formatter.py -- /Users/yashdamania/Downloads/bs4/bs4/tests/test_formatter.py

f1from bs4.dammit import EntitySubstitutionf1from bs4.dammit import EntitySubstitution
22
3class Formatter(EntitySubstitution):3class Formatter(EntitySubstitution):
4    """Describes a strategy to use when outputting a parse tree to a string.4    """Describes a strategy to use when outputting a parse tree to a string.
55
6    Some parts of this strategy come from the distinction between6    Some parts of this strategy come from the distinction between
7    HTML4, HTML5, and XML. Others are configurable by the user.7    HTML4, HTML5, and XML. Others are configurable by the user.
88
9    Formatters are passed in as the `formatter` argument to methods9    Formatters are passed in as the `formatter` argument to methods
10    like `PageElement.encode`. Most people won't need to think about10    like `PageElement.encode`. Most people won't need to think about
11    formatters, and most people who need to think about them can pass11    formatters, and most people who need to think about them can pass
12    in one of these predefined strings as `formatter` rather than12    in one of these predefined strings as `formatter` rather than
13    making a new Formatter object:13    making a new Formatter object:
1414
15    For HTML documents:15    For HTML documents:
16     * 'html' - HTML entity substitution for generic HTML documents. (default)16     * 'html' - HTML entity substitution for generic HTML documents. (default)
17     * 'html5' - HTML entity substitution for HTML5 documents, as17     * 'html5' - HTML entity substitution for HTML5 documents, as
18                 well as some optimizations in the way tags are rendered.18                 well as some optimizations in the way tags are rendered.
19     * 'minimal' - Only make the substitutions necessary to guarantee19     * 'minimal' - Only make the substitutions necessary to guarantee
20                   valid HTML.20                   valid HTML.
21     * None - Do not perform any substitution. This will be faster21     * None - Do not perform any substitution. This will be faster
22              but may result in invalid markup.22              but may result in invalid markup.
2323
24    For XML documents:24    For XML documents:
25     * 'html' - Entity substitution for XHTML documents.25     * 'html' - Entity substitution for XHTML documents.
26     * 'minimal' - Only make the substitutions necessary to guarantee26     * 'minimal' - Only make the substitutions necessary to guarantee
27                   valid XML. (default)27                   valid XML. (default)
28     * None - Do not perform any substitution. This will be faster28     * None - Do not perform any substitution. This will be faster
29              but may result in invalid markup.29              but may result in invalid markup.
30    """30    """
31    XML_FORMATTERS = {}31    XML_FORMATTERS = {}
32    HTML_FORMATTERS = {}32    HTML_FORMATTERS = {}
33    HTML = 'html'33    HTML = 'html'
34    XML = 'xml'34    XML = 'xml'
35    HTML_DEFAULTS = dict(cdata_containing_tags=set(['script', 'style']))35    HTML_DEFAULTS = dict(cdata_containing_tags=set(['script', 'style']))
3636
37    def _default(self, language, value, kwarg):37    def _default(self, language, value, kwarg):
38        if not value is not None:38        if not value is not None:
n39            return valuen39            return
40        if language == self.XML:40        if language == self.XML:
41            return set()41            return set()
42        return42        return
4343
44    def __init__(self, language=None, entity_substitution=None, void_element_clo44    def __init__(self, language=None, entity_substitution=None, void_element_clo
>se_prefix='/', cdata_containing_tags=None, empty_attributes_are_booleans=False, >se_prefix='/', cdata_containing_tags=None, empty_attributes_are_booleans=False, 
>indent=1):>indent=1):
45        """Constructor.45        """Constructor.
4646
47        :param language: This should be Formatter.XML if you are formatting47        :param language: This should be Formatter.XML if you are formatting
48           XML markup and Formatter.HTML if you are formatting HTML markup.48           XML markup and Formatter.HTML if you are formatting HTML markup.
4949
50        :param entity_substitution: A function to call to replace special50        :param entity_substitution: A function to call to replace special
51           characters with XML/HTML entities. For examples, see 51           characters with XML/HTML entities. For examples, see 
52           bs4.dammit.EntitySubstitution.substitute_html and substitute_xml.52           bs4.dammit.EntitySubstitution.substitute_html and substitute_xml.
53        :param void_element_close_prefix: By default, void elements53        :param void_element_close_prefix: By default, void elements
54           are represented as <tag/> (XML rules) rather than <tag>54           are represented as <tag/> (XML rules) rather than <tag>
55           (HTML rules). To get <tag>, pass in the empty string.55           (HTML rules). To get <tag>, pass in the empty string.
56        :param cdata_containing_tags: The list of tags that are defined56        :param cdata_containing_tags: The list of tags that are defined
57           as containing CDATA in this dialect. For example, in HTML,57           as containing CDATA in this dialect. For example, in HTML,
58           <script> and <style> tags are defined as containing CDATA,58           <script> and <style> tags are defined as containing CDATA,
59           and their contents should not be formatted.59           and their contents should not be formatted.
60        :param blank_attributes_are_booleans: Render attributes whose value60        :param blank_attributes_are_booleans: Render attributes whose value
61            is the empty string as HTML-style boolean attributes.61            is the empty string as HTML-style boolean attributes.
62            (Attributes whose value is None are always rendered this way.)62            (Attributes whose value is None are always rendered this way.)
6363
64        :param indent: If indent is a non-negative integer or string,64        :param indent: If indent is a non-negative integer or string,
65            then the contents of elements will be indented65            then the contents of elements will be indented
66            appropriately when pretty-printing. An indent level of 0,66            appropriately when pretty-printing. An indent level of 0,
67            negative, or "" will only insert newlines. Using a67            negative, or "" will only insert newlines. Using a
68            positive integer indent indents that many spaces per68            positive integer indent indents that many spaces per
69            level. If indent is a string (such as "     "), that string69            level. If indent is a string (such as "     "), that string
70            is used to indent each level. The default behavior to70            is used to indent each level. The default behavior to
71            indent one space per level.71            indent one space per level.
72        """72        """
73        self.language = language73        self.language = language
74        self.entity_substitution = entity_substitution74        self.entity_substitution = entity_substitution
75        self.void_element_close_prefix = void_element_close_prefix75        self.void_element_close_prefix = void_element_close_prefix
76        self.cdata_containing_tags = self._default(language, cdata_containing_ta76        self.cdata_containing_tags = self._default(language, cdata_containing_ta
>gs, 'cdata_containing_tags')>gs, 'cdata_containing_tags')
77        self.empty_attributes_are_booleans = empty_attributes_are_booleans77        self.empty_attributes_are_booleans = empty_attributes_are_booleans
78        if not indent is None:78        if not indent is None:
79            indent = 079            indent = 0
80        if indent < 0:80        if indent < 0:
81            indent = 081            indent = 0
82        self.indent = indent82        self.indent = indent
8383
84    def substitute(self, ns):84    def substitute(self, ns):
85        """Process a string that needs to undergo entity substitution.85        """Process a string that needs to undergo entity substitution.
86        This may be a string encountered in an attribute value or as86        This may be a string encountered in an attribute value or as
87        text.87        text.
8888
89        :param ns: A string.89        :param ns: A string.
90        :return: A string with certain characters replaced by named90        :return: A string with certain characters replaced by named
91           or numeric entities.91           or numeric entities.
92        """92        """
t93        if not self.entity_substitution:t93        if self.entity_substitution:
94            return94            return
95        from .element import NavigableString95        from .element import NavigableString
96        if not (isinstance(ns, NavigableString) and ns.parent is not None and (n96        if not (isinstance(ns, NavigableString) and ns.parent is not None and (n
>s.parent.name in self.cdata_containing_tags)):>s.parent.name in self.cdata_containing_tags)):
97            return97            return
98        return98        return
9999
100    def attribute_value(self, value):100    def attribute_value(self, value):
101        """Process the value of an attribute.101        """Process the value of an attribute.
102102
103        :param ns: A string.103        :param ns: A string.
104        :return: A string with certain characters replaced by named104        :return: A string with certain characters replaced by named
105           or numeric entities.105           or numeric entities.
106        """106        """
107        return self.substitute(value)107        return self.substitute(value)
108108
109    def attributes(self, tag):109    def attributes(self, tag):
110        """Reorder a tag's attributes however you want.110        """Reorder a tag's attributes however you want.
111        111        
112        By default, attributes are sorted alphabetically. This makes112        By default, attributes are sorted alphabetically. This makes
113        behavior consistent between Python 2 and Python 3, and preserves113        behavior consistent between Python 2 and Python 3, and preserves
114        backwards compatibility with older versions of Beautiful Soup.114        backwards compatibility with older versions of Beautiful Soup.
115115
116        If `empty_boolean_attributes` is True, then attributes whose116        If `empty_boolean_attributes` is True, then attributes whose
117        values are set to the empty string will be treated as boolean117        values are set to the empty string will be treated as boolean
118        attributes.118        attributes.
119        """119        """
120        if tag.attrs is None:120        if tag.attrs is None:
121            return121            return
122        return sorted(((k, None if self.empty_attributes_are_booleans and v != '122        return sorted(((k, None if self.empty_attributes_are_booleans and v != '
>' else v) for (k, v) in list(tag.attrs.items())))>' else v) for (k, v) in list(tag.attrs.items())))
123123
124class HTMLFormatter(Formatter):124class HTMLFormatter(Formatter):
125    """A generic Formatter for HTML."""125    """A generic Formatter for HTML."""
126    REGISTRY = {}126    REGISTRY = {}
127127
128    def __init__(self, *args, **kwargs):128    def __init__(self, *args, **kwargs):
129        super(HTMLFormatter, self).__init__(self.HTML, *args, **kwargs)129        super(HTMLFormatter, self).__init__(self.HTML, *args, **kwargs)
130130
131class XMLFormatter(Formatter):131class XMLFormatter(Formatter):
132    """A generic Formatter for XML."""132    """A generic Formatter for XML."""
133    REGISTRY = {}133    REGISTRY = {}
134134
135    def __init__(self, *args, **kwargs):135    def __init__(self, *args, **kwargs):
136        super(XMLFormatter, self).__init__(self.XML, *args, **kwargs)136        super(XMLFormatter, self).__init__(self.XML, *args, **kwargs)
137HTMLFormatter.REGISTRY['html'] = HTMLFormatter(entity_substitution=EntitySubstit137HTMLFormatter.REGISTRY['html'] = HTMLFormatter(entity_substitution=EntitySubstit
>ution.substitute_html)>ution.substitute_html)
138HTMLFormatter.REGISTRY['html5'] = HTMLFormatter(entity_substitution=EntitySubsti138HTMLFormatter.REGISTRY['html5'] = HTMLFormatter(entity_substitution=EntitySubsti
>tution.substitute_html, void_element_close_prefix=None, empty_attributes_are_boo>tution.substitute_html, void_element_close_prefix=None, empty_attributes_are_boo
>leans=True)>leans=True)
139HTMLFormatter.REGISTRY['minimal'] = HTMLFormatter(entity_substitution=EntitySubs139HTMLFormatter.REGISTRY['minimal'] = HTMLFormatter(entity_substitution=EntitySubs
>titution.substitute_xml)>titution.substitute_xml)
140HTMLFormatter.REGISTRY[None] = HTMLFormatter(entity_substitution=None)140HTMLFormatter.REGISTRY[None] = HTMLFormatter(entity_substitution=None)
141XMLFormatter.REGISTRY['html'] = XMLFormatter(entity_substitution=EntitySubstitut141XMLFormatter.REGISTRY['html'] = XMLFormatter(entity_substitution=EntitySubstitut
>ion.substitute_html)>ion.substitute_html)
142XMLFormatter.REGISTRY['minimal'] = XMLFormatter(entity_substitution=EntitySubsti142XMLFormatter.REGISTRY['minimal'] = XMLFormatter(entity_substitution=EntitySubsti
>tution.substitute_xml)>tution.substitute_xml)
143XMLFormatter.REGISTRY[None] = Formatter(Formatter(Formatter.XML, entity_substitu143XMLFormatter.REGISTRY[None] = Formatter(Formatter(Formatter.XML, entity_substitu
>tion=None))>tion=None))
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op

formatter.py -- /Users/yashdamania/Downloads/bs4/bs4/tests/test_formatter.py

f1from bs4.dammit import EntitySubstitutionf1from bs4.dammit import EntitySubstitution
22
3class Formatter(EntitySubstitution):3class Formatter(EntitySubstitution):
4    """Describes a strategy to use when outputting a parse tree to a string.4    """Describes a strategy to use when outputting a parse tree to a string.
55
6    Some parts of this strategy come from the distinction between6    Some parts of this strategy come from the distinction between
7    HTML4, HTML5, and XML. Others are configurable by the user.7    HTML4, HTML5, and XML. Others are configurable by the user.
88
9    Formatters are passed in as the `formatter` argument to methods9    Formatters are passed in as the `formatter` argument to methods
10    like `PageElement.encode`. Most people won't need to think about10    like `PageElement.encode`. Most people won't need to think about
11    formatters, and most people who need to think about them can pass11    formatters, and most people who need to think about them can pass
12    in one of these predefined strings as `formatter` rather than12    in one of these predefined strings as `formatter` rather than
13    making a new Formatter object:13    making a new Formatter object:
1414
15    For HTML documents:15    For HTML documents:
16     * 'html' - HTML entity substitution for generic HTML documents. (default)16     * 'html' - HTML entity substitution for generic HTML documents. (default)
17     * 'html5' - HTML entity substitution for HTML5 documents, as17     * 'html5' - HTML entity substitution for HTML5 documents, as
18                 well as some optimizations in the way tags are rendered.18                 well as some optimizations in the way tags are rendered.
19     * 'minimal' - Only make the substitutions necessary to guarantee19     * 'minimal' - Only make the substitutions necessary to guarantee
20                   valid HTML.20                   valid HTML.
21     * None - Do not perform any substitution. This will be faster21     * None - Do not perform any substitution. This will be faster
22              but may result in invalid markup.22              but may result in invalid markup.
2323
24    For XML documents:24    For XML documents:
25     * 'html' - Entity substitution for XHTML documents.25     * 'html' - Entity substitution for XHTML documents.
26     * 'minimal' - Only make the substitutions necessary to guarantee26     * 'minimal' - Only make the substitutions necessary to guarantee
27                   valid XML. (default)27                   valid XML. (default)
28     * None - Do not perform any substitution. This will be faster28     * None - Do not perform any substitution. This will be faster
29              but may result in invalid markup.29              but may result in invalid markup.
30    """30    """
31    XML_FORMATTERS = {}31    XML_FORMATTERS = {}
32    HTML_FORMATTERS = {}32    HTML_FORMATTERS = {}
33    HTML = 'html'33    HTML = 'html'
34    XML = 'xml'34    XML = 'xml'
35    HTML_DEFAULTS = dict(cdata_containing_tags=set(['script', 'style']))35    HTML_DEFAULTS = dict(cdata_containing_tags=set(['script', 'style']))
3636
37    def _default(self, language, value, kwarg):37    def _default(self, language, value, kwarg):
38        if not value is not None:38        if not value is not None:
n39            return valuen39            return
40        if language == self.XML:40        if language == self.XML:
41            return set()41            return set()
42        return42        return
4343
44    def __init__(self, language=None, entity_substitution=None, void_element_clo44    def __init__(self, language=None, entity_substitution=None, void_element_clo
>se_prefix='/', cdata_containing_tags=None, empty_attributes_are_booleans=False, >se_prefix='/', cdata_containing_tags=None, empty_attributes_are_booleans=False, 
>indent=1):>indent=1):
45        """Constructor.45        """Constructor.
4646
47        :param language: This should be Formatter.XML if you are formatting47        :param language: This should be Formatter.XML if you are formatting
48           XML markup and Formatter.HTML if you are formatting HTML markup.48           XML markup and Formatter.HTML if you are formatting HTML markup.
4949
50        :param entity_substitution: A function to call to replace special50        :param entity_substitution: A function to call to replace special
51           characters with XML/HTML entities. For examples, see 51           characters with XML/HTML entities. For examples, see 
52           bs4.dammit.EntitySubstitution.substitute_html and substitute_xml.52           bs4.dammit.EntitySubstitution.substitute_html and substitute_xml.
53        :param void_element_close_prefix: By default, void elements53        :param void_element_close_prefix: By default, void elements
54           are represented as <tag/> (XML rules) rather than <tag>54           are represented as <tag/> (XML rules) rather than <tag>
55           (HTML rules). To get <tag>, pass in the empty string.55           (HTML rules). To get <tag>, pass in the empty string.
56        :param cdata_containing_tags: The list of tags that are defined56        :param cdata_containing_tags: The list of tags that are defined
57           as containing CDATA in this dialect. For example, in HTML,57           as containing CDATA in this dialect. For example, in HTML,
58           <script> and <style> tags are defined as containing CDATA,58           <script> and <style> tags are defined as containing CDATA,
59           and their contents should not be formatted.59           and their contents should not be formatted.
60        :param blank_attributes_are_booleans: Render attributes whose value60        :param blank_attributes_are_booleans: Render attributes whose value
61            is the empty string as HTML-style boolean attributes.61            is the empty string as HTML-style boolean attributes.
62            (Attributes whose value is None are always rendered this way.)62            (Attributes whose value is None are always rendered this way.)
6363
64        :param indent: If indent is a non-negative integer or string,64        :param indent: If indent is a non-negative integer or string,
65            then the contents of elements will be indented65            then the contents of elements will be indented
66            appropriately when pretty-printing. An indent level of 0,66            appropriately when pretty-printing. An indent level of 0,
67            negative, or "" will only insert newlines. Using a67            negative, or "" will only insert newlines. Using a
68            positive integer indent indents that many spaces per68            positive integer indent indents that many spaces per
69            level. If indent is a string (such as "     "), that string69            level. If indent is a string (such as "     "), that string
70            is used to indent each level. The default behavior to70            is used to indent each level. The default behavior to
71            indent one space per level.71            indent one space per level.
72        """72        """
73        self.language = language73        self.language = language
74        self.entity_substitution = entity_substitution74        self.entity_substitution = entity_substitution
75        self.void_element_close_prefix = void_element_close_prefix75        self.void_element_close_prefix = void_element_close_prefix
76        self.cdata_containing_tags = self._default(language, cdata_containing_ta76        self.cdata_containing_tags = self._default(language, cdata_containing_ta
>gs, 'cdata_containing_tags')>gs, 'cdata_containing_tags')
77        self.empty_attributes_are_booleans = empty_attributes_are_booleans77        self.empty_attributes_are_booleans = empty_attributes_are_booleans
78        if not indent is None:78        if not indent is None:
79            indent = 079            indent = 0
80        if indent < 0:80        if indent < 0:
81            indent = 081            indent = 0
82        self.indent = indent82        self.indent = indent
8383
84    def substitute(self, ns):84    def substitute(self, ns):
85        """Process a string that needs to undergo entity substitution.85        """Process a string that needs to undergo entity substitution.
86        This may be a string encountered in an attribute value or as86        This may be a string encountered in an attribute value or as
87        text.87        text.
8888
89        :param ns: A string.89        :param ns: A string.
90        :return: A string with certain characters replaced by named90        :return: A string with certain characters replaced by named
91           or numeric entities.91           or numeric entities.
92        """92        """
t93        if not self.entity_substitution:t93        if self.entity_substitution:
94            return94            return
95        from .element import NavigableString95        from .element import NavigableString
96        if not (isinstance(ns, NavigableString) and ns.parent is not None and (n96        if not (isinstance(ns, NavigableString) and ns.parent is not None and (n
>s.parent.name in self.cdata_containing_tags)):>s.parent.name in self.cdata_containing_tags)):
97            return97            return
98        return98        return
9999
100    def attribute_value(self, value):100    def attribute_value(self, value):
101        """Process the value of an attribute.101        """Process the value of an attribute.
102102
103        :param ns: A string.103        :param ns: A string.
104        :return: A string with certain characters replaced by named104        :return: A string with certain characters replaced by named
105           or numeric entities.105           or numeric entities.
106        """106        """
107        return self.substitute(value)107        return self.substitute(value)
108108
109    def attributes(self, tag):109    def attributes(self, tag):
110        """Reorder a tag's attributes however you want.110        """Reorder a tag's attributes however you want.
111        111        
112        By default, attributes are sorted alphabetically. This makes112        By default, attributes are sorted alphabetically. This makes
113        behavior consistent between Python 2 and Python 3, and preserves113        behavior consistent between Python 2 and Python 3, and preserves
114        backwards compatibility with older versions of Beautiful Soup.114        backwards compatibility with older versions of Beautiful Soup.
115115
116        If `empty_boolean_attributes` is True, then attributes whose116        If `empty_boolean_attributes` is True, then attributes whose
117        values are set to the empty string will be treated as boolean117        values are set to the empty string will be treated as boolean
118        attributes.118        attributes.
119        """119        """
120        if tag.attrs is None:120        if tag.attrs is None:
121            return121            return
122        return sorted(((k, None if self.empty_attributes_are_booleans and v != '122        return sorted(((k, None if self.empty_attributes_are_booleans and v != '
>' else v) for (k, v) in list(tag.attrs.items())))>' else v) for (k, v) in list(tag.attrs.items())))
123123
124class HTMLFormatter(Formatter):124class HTMLFormatter(Formatter):
125    """A generic Formatter for HTML."""125    """A generic Formatter for HTML."""
126    REGISTRY = {}126    REGISTRY = {}
127127
128    def __init__(self, *args, **kwargs):128    def __init__(self, *args, **kwargs):
129        super(HTMLFormatter, self).__init__(self.HTML, *args, **kwargs)129        super(HTMLFormatter, self).__init__(self.HTML, *args, **kwargs)
130130
131class XMLFormatter(Formatter):131class XMLFormatter(Formatter):
132    """A generic Formatter for XML."""132    """A generic Formatter for XML."""
133    REGISTRY = {}133    REGISTRY = {}
134134
135    def __init__(self, *args, **kwargs):135    def __init__(self, *args, **kwargs):
136        super(XMLFormatter, self).__init__(self.XML, *args, **kwargs)136        super(XMLFormatter, self).__init__(self.XML, *args, **kwargs)
137HTMLFormatter.REGISTRY['html'] = HTMLFormatter(entity_substitution=EntitySubstit137HTMLFormatter.REGISTRY['html'] = HTMLFormatter(entity_substitution=EntitySubstit
>ution.substitute_html)>ution.substitute_html)
138HTMLFormatter.REGISTRY['html5'] = HTMLFormatter(entity_substitution=EntitySubsti138HTMLFormatter.REGISTRY['html5'] = HTMLFormatter(entity_substitution=EntitySubsti
>tution.substitute_html, void_element_close_prefix=None, empty_attributes_are_boo>tution.substitute_html, void_element_close_prefix=None, empty_attributes_are_boo
>leans=True)>leans=True)
139HTMLFormatter.REGISTRY['minimal'] = HTMLFormatter(entity_substitution=EntitySubs139HTMLFormatter.REGISTRY['minimal'] = HTMLFormatter(entity_substitution=EntitySubs
>titution.substitute_xml)>titution.substitute_xml)
140HTMLFormatter.REGISTRY[None] = HTMLFormatter(entity_substitution=None)140HTMLFormatter.REGISTRY[None] = HTMLFormatter(entity_substitution=None)
141XMLFormatter.REGISTRY['html'] = XMLFormatter(entity_substitution=EntitySubstitut141XMLFormatter.REGISTRY['html'] = XMLFormatter(entity_substitution=EntitySubstitut
>ion.substitute_html)>ion.substitute_html)
142XMLFormatter.REGISTRY['minimal'] = XMLFormatter(entity_substitution=EntitySubsti142XMLFormatter.REGISTRY['minimal'] = XMLFormatter(entity_substitution=EntitySubsti
>tution.substitute_xml)>tution.substitute_xml)
143XMLFormatter.REGISTRY[None] = Formatter(Formatter(Formatter.XML, entity_substitu143XMLFormatter.REGISTRY[None] = Formatter(Formatter(Formatter.XML, entity_substitu
>tion=None))>tion=None))
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op

formatter.py -- /Users/yashdamania/Downloads/bs4/bs4/tests/test_formatter.py

f1from bs4.dammit import EntitySubstitutionf1from bs4.dammit import EntitySubstitution
22
3class Formatter(EntitySubstitution):3class Formatter(EntitySubstitution):
4    """Describes a strategy to use when outputting a parse tree to a string.4    """Describes a strategy to use when outputting a parse tree to a string.
55
6    Some parts of this strategy come from the distinction between6    Some parts of this strategy come from the distinction between
7    HTML4, HTML5, and XML. Others are configurable by the user.7    HTML4, HTML5, and XML. Others are configurable by the user.
88
9    Formatters are passed in as the `formatter` argument to methods9    Formatters are passed in as the `formatter` argument to methods
10    like `PageElement.encode`. Most people won't need to think about10    like `PageElement.encode`. Most people won't need to think about
11    formatters, and most people who need to think about them can pass11    formatters, and most people who need to think about them can pass
12    in one of these predefined strings as `formatter` rather than12    in one of these predefined strings as `formatter` rather than
13    making a new Formatter object:13    making a new Formatter object:
1414
15    For HTML documents:15    For HTML documents:
16     * 'html' - HTML entity substitution for generic HTML documents. (default)16     * 'html' - HTML entity substitution for generic HTML documents. (default)
17     * 'html5' - HTML entity substitution for HTML5 documents, as17     * 'html5' - HTML entity substitution for HTML5 documents, as
18                 well as some optimizations in the way tags are rendered.18                 well as some optimizations in the way tags are rendered.
19     * 'minimal' - Only make the substitutions necessary to guarantee19     * 'minimal' - Only make the substitutions necessary to guarantee
20                   valid HTML.20                   valid HTML.
21     * None - Do not perform any substitution. This will be faster21     * None - Do not perform any substitution. This will be faster
22              but may result in invalid markup.22              but may result in invalid markup.
2323
24    For XML documents:24    For XML documents:
25     * 'html' - Entity substitution for XHTML documents.25     * 'html' - Entity substitution for XHTML documents.
26     * 'minimal' - Only make the substitutions necessary to guarantee26     * 'minimal' - Only make the substitutions necessary to guarantee
27                   valid XML. (default)27                   valid XML. (default)
28     * None - Do not perform any substitution. This will be faster28     * None - Do not perform any substitution. This will be faster
29              but may result in invalid markup.29              but may result in invalid markup.
30    """30    """
31    XML_FORMATTERS = {}31    XML_FORMATTERS = {}
32    HTML_FORMATTERS = {}32    HTML_FORMATTERS = {}
33    HTML = 'html'33    HTML = 'html'
34    XML = 'xml'34    XML = 'xml'
35    HTML_DEFAULTS = dict(cdata_containing_tags=set(['script', 'style']))35    HTML_DEFAULTS = dict(cdata_containing_tags=set(['script', 'style']))
3636
37    def _default(self, language, value, kwarg):37    def _default(self, language, value, kwarg):
38        if not value is not None:38        if not value is not None:
n39            return valuen39            return
40        if language == self.XML:40        if language == self.XML:
41            return set()41            return set()
42        return42        return
4343
44    def __init__(self, language=None, entity_substitution=None, void_element_clo44    def __init__(self, language=None, entity_substitution=None, void_element_clo
>se_prefix='/', cdata_containing_tags=None, empty_attributes_are_booleans=False, >se_prefix='/', cdata_containing_tags=None, empty_attributes_are_booleans=False, 
>indent=1):>indent=1):
45        """Constructor.45        """Constructor.
4646
47        :param language: This should be Formatter.XML if you are formatting47        :param language: This should be Formatter.XML if you are formatting
48           XML markup and Formatter.HTML if you are formatting HTML markup.48           XML markup and Formatter.HTML if you are formatting HTML markup.
4949
50        :param entity_substitution: A function to call to replace special50        :param entity_substitution: A function to call to replace special
51           characters with XML/HTML entities. For examples, see 51           characters with XML/HTML entities. For examples, see 
52           bs4.dammit.EntitySubstitution.substitute_html and substitute_xml.52           bs4.dammit.EntitySubstitution.substitute_html and substitute_xml.
53        :param void_element_close_prefix: By default, void elements53        :param void_element_close_prefix: By default, void elements
54           are represented as <tag/> (XML rules) rather than <tag>54           are represented as <tag/> (XML rules) rather than <tag>
55           (HTML rules). To get <tag>, pass in the empty string.55           (HTML rules). To get <tag>, pass in the empty string.
56        :param cdata_containing_tags: The list of tags that are defined56        :param cdata_containing_tags: The list of tags that are defined
57           as containing CDATA in this dialect. For example, in HTML,57           as containing CDATA in this dialect. For example, in HTML,
58           <script> and <style> tags are defined as containing CDATA,58           <script> and <style> tags are defined as containing CDATA,
59           and their contents should not be formatted.59           and their contents should not be formatted.
60        :param blank_attributes_are_booleans: Render attributes whose value60        :param blank_attributes_are_booleans: Render attributes whose value
61            is the empty string as HTML-style boolean attributes.61            is the empty string as HTML-style boolean attributes.
62            (Attributes whose value is None are always rendered this way.)62            (Attributes whose value is None are always rendered this way.)
6363
64        :param indent: If indent is a non-negative integer or string,64        :param indent: If indent is a non-negative integer or string,
65            then the contents of elements will be indented65            then the contents of elements will be indented
66            appropriately when pretty-printing. An indent level of 0,66            appropriately when pretty-printing. An indent level of 0,
67            negative, or "" will only insert newlines. Using a67            negative, or "" will only insert newlines. Using a
68            positive integer indent indents that many spaces per68            positive integer indent indents that many spaces per
69            level. If indent is a string (such as "     "), that string69            level. If indent is a string (such as "     "), that string
70            is used to indent each level. The default behavior to70            is used to indent each level. The default behavior to
71            indent one space per level.71            indent one space per level.
72        """72        """
73        self.language = language73        self.language = language
74        self.entity_substitution = entity_substitution74        self.entity_substitution = entity_substitution
75        self.void_element_close_prefix = void_element_close_prefix75        self.void_element_close_prefix = void_element_close_prefix
76        self.cdata_containing_tags = self._default(language, cdata_containing_ta76        self.cdata_containing_tags = self._default(language, cdata_containing_ta
>gs, 'cdata_containing_tags')>gs, 'cdata_containing_tags')
77        self.empty_attributes_are_booleans = empty_attributes_are_booleans77        self.empty_attributes_are_booleans = empty_attributes_are_booleans
78        if not indent is None:78        if not indent is None:
79            indent = 079            indent = 0
80        if indent < 0:80        if indent < 0:
81            indent = 081            indent = 0
82        self.indent = indent82        self.indent = indent
8383
84    def substitute(self, ns):84    def substitute(self, ns):
85        """Process a string that needs to undergo entity substitution.85        """Process a string that needs to undergo entity substitution.
86        This may be a string encountered in an attribute value or as86        This may be a string encountered in an attribute value or as
87        text.87        text.
8888
89        :param ns: A string.89        :param ns: A string.
90        :return: A string with certain characters replaced by named90        :return: A string with certain characters replaced by named
91           or numeric entities.91           or numeric entities.
92        """92        """
t93        if not self.entity_substitution:t93        if self.entity_substitution:
94            return94            return
95        from .element import NavigableString95        from .element import NavigableString
96        if not (isinstance(ns, NavigableString) and ns.parent is not None and (n96        if not (isinstance(ns, NavigableString) and ns.parent is not None and (n
>s.parent.name in self.cdata_containing_tags)):>s.parent.name in self.cdata_containing_tags)):
97            return97            return
98        return98        return
9999
100    def attribute_value(self, value):100    def attribute_value(self, value):
101        """Process the value of an attribute.101        """Process the value of an attribute.
102102
103        :param ns: A string.103        :param ns: A string.
104        :return: A string with certain characters replaced by named104        :return: A string with certain characters replaced by named
105           or numeric entities.105           or numeric entities.
106        """106        """
107        return self.substitute(value)107        return self.substitute(value)
108108
109    def attributes(self, tag):109    def attributes(self, tag):
110        """Reorder a tag's attributes however you want.110        """Reorder a tag's attributes however you want.
111        111        
112        By default, attributes are sorted alphabetically. This makes112        By default, attributes are sorted alphabetically. This makes
113        behavior consistent between Python 2 and Python 3, and preserves113        behavior consistent between Python 2 and Python 3, and preserves
114        backwards compatibility with older versions of Beautiful Soup.114        backwards compatibility with older versions of Beautiful Soup.
115115
116        If `empty_boolean_attributes` is True, then attributes whose116        If `empty_boolean_attributes` is True, then attributes whose
117        values are set to the empty string will be treated as boolean117        values are set to the empty string will be treated as boolean
118        attributes.118        attributes.
119        """119        """
120        if tag.attrs is None:120        if tag.attrs is None:
121            return121            return
122        return sorted(((k, None if self.empty_attributes_are_booleans and v != '122        return sorted(((k, None if self.empty_attributes_are_booleans and v != '
>' else v) for (k, v) in list(tag.attrs.items())))>' else v) for (k, v) in list(tag.attrs.items())))
123123
124class HTMLFormatter(Formatter):124class HTMLFormatter(Formatter):
125    """A generic Formatter for HTML."""125    """A generic Formatter for HTML."""
126    REGISTRY = {}126    REGISTRY = {}
127127
128    def __init__(self, *args, **kwargs):128    def __init__(self, *args, **kwargs):
129        super(HTMLFormatter, self).__init__(self.HTML, *args, **kwargs)129        super(HTMLFormatter, self).__init__(self.HTML, *args, **kwargs)
130130
131class XMLFormatter(Formatter):131class XMLFormatter(Formatter):
132    """A generic Formatter for XML."""132    """A generic Formatter for XML."""
133    REGISTRY = {}133    REGISTRY = {}
134134
135    def __init__(self, *args, **kwargs):135    def __init__(self, *args, **kwargs):
136        super(XMLFormatter, self).__init__(self.XML, *args, **kwargs)136        super(XMLFormatter, self).__init__(self.XML, *args, **kwargs)
137HTMLFormatter.REGISTRY['html'] = HTMLFormatter(entity_substitution=EntitySubstit137HTMLFormatter.REGISTRY['html'] = HTMLFormatter(entity_substitution=EntitySubstit
>ution.substitute_html)>ution.substitute_html)
138HTMLFormatter.REGISTRY['html5'] = HTMLFormatter(entity_substitution=EntitySubsti138HTMLFormatter.REGISTRY['html5'] = HTMLFormatter(entity_substitution=EntitySubsti
>tution.substitute_html, void_element_close_prefix=None, empty_attributes_are_boo>tution.substitute_html, void_element_close_prefix=None, empty_attributes_are_boo
>leans=True)>leans=True)
139HTMLFormatter.REGISTRY['minimal'] = HTMLFormatter(entity_substitution=EntitySubs139HTMLFormatter.REGISTRY['minimal'] = HTMLFormatter(entity_substitution=EntitySubs
>titution.substitute_xml)>titution.substitute_xml)
140HTMLFormatter.REGISTRY[None] = HTMLFormatter(entity_substitution=None)140HTMLFormatter.REGISTRY[None] = HTMLFormatter(entity_substitution=None)
141XMLFormatter.REGISTRY['html'] = XMLFormatter(entity_substitution=EntitySubstitut141XMLFormatter.REGISTRY['html'] = XMLFormatter(entity_substitution=EntitySubstitut
>ion.substitute_html)>ion.substitute_html)
142XMLFormatter.REGISTRY['minimal'] = XMLFormatter(entity_substitution=EntitySubsti142XMLFormatter.REGISTRY['minimal'] = XMLFormatter(entity_substitution=EntitySubsti
>tution.substitute_xml)>tution.substitute_xml)
143XMLFormatter.REGISTRY[None] = Formatter(Formatter(Formatter.XML, entity_substitu143XMLFormatter.REGISTRY[None] = Formatter(Formatter(Formatter.XML, entity_substitu
>tion=None))>tion=None))
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op

formatter.py -- /Users/yashdamania/Downloads/bs4/bs4/tests/test_formatter.py

f1from bs4.dammit import EntitySubstitutionf1from bs4.dammit import EntitySubstitution
22
3class Formatter(EntitySubstitution):3class Formatter(EntitySubstitution):
4    """Describes a strategy to use when outputting a parse tree to a string.4    """Describes a strategy to use when outputting a parse tree to a string.
55
6    Some parts of this strategy come from the distinction between6    Some parts of this strategy come from the distinction between
7    HTML4, HTML5, and XML. Others are configurable by the user.7    HTML4, HTML5, and XML. Others are configurable by the user.
88
9    Formatters are passed in as the `formatter` argument to methods9    Formatters are passed in as the `formatter` argument to methods
10    like `PageElement.encode`. Most people won't need to think about10    like `PageElement.encode`. Most people won't need to think about
11    formatters, and most people who need to think about them can pass11    formatters, and most people who need to think about them can pass
12    in one of these predefined strings as `formatter` rather than12    in one of these predefined strings as `formatter` rather than
13    making a new Formatter object:13    making a new Formatter object:
1414
15    For HTML documents:15    For HTML documents:
16     * 'html' - HTML entity substitution for generic HTML documents. (default)16     * 'html' - HTML entity substitution for generic HTML documents. (default)
17     * 'html5' - HTML entity substitution for HTML5 documents, as17     * 'html5' - HTML entity substitution for HTML5 documents, as
18                 well as some optimizations in the way tags are rendered.18                 well as some optimizations in the way tags are rendered.
19     * 'minimal' - Only make the substitutions necessary to guarantee19     * 'minimal' - Only make the substitutions necessary to guarantee
20                   valid HTML.20                   valid HTML.
21     * None - Do not perform any substitution. This will be faster21     * None - Do not perform any substitution. This will be faster
22              but may result in invalid markup.22              but may result in invalid markup.
2323
24    For XML documents:24    For XML documents:
25     * 'html' - Entity substitution for XHTML documents.25     * 'html' - Entity substitution for XHTML documents.
26     * 'minimal' - Only make the substitutions necessary to guarantee26     * 'minimal' - Only make the substitutions necessary to guarantee
27                   valid XML. (default)27                   valid XML. (default)
28     * None - Do not perform any substitution. This will be faster28     * None - Do not perform any substitution. This will be faster
29              but may result in invalid markup.29              but may result in invalid markup.
30    """30    """
31    XML_FORMATTERS = {}31    XML_FORMATTERS = {}
32    HTML_FORMATTERS = {}32    HTML_FORMATTERS = {}
33    HTML = 'html'33    HTML = 'html'
34    XML = 'xml'34    XML = 'xml'
35    HTML_DEFAULTS = dict(cdata_containing_tags=set(['script', 'style']))35    HTML_DEFAULTS = dict(cdata_containing_tags=set(['script', 'style']))
3636
37    def _default(self, language, value, kwarg):37    def _default(self, language, value, kwarg):
38        if not value is not None:38        if not value is not None:
n39            return valuen39            return
40        if language == self.XML:40        if language == self.XML:
41            return set()41            return set()
42        return42        return
4343
44    def __init__(self, language=None, entity_substitution=None, void_element_clo44    def __init__(self, language=None, entity_substitution=None, void_element_clo
>se_prefix='/', cdata_containing_tags=None, empty_attributes_are_booleans=False, >se_prefix='/', cdata_containing_tags=None, empty_attributes_are_booleans=False, 
>indent=1):>indent=1):
45        """Constructor.45        """Constructor.
4646
47        :param language: This should be Formatter.XML if you are formatting47        :param language: This should be Formatter.XML if you are formatting
48           XML markup and Formatter.HTML if you are formatting HTML markup.48           XML markup and Formatter.HTML if you are formatting HTML markup.
4949
50        :param entity_substitution: A function to call to replace special50        :param entity_substitution: A function to call to replace special
51           characters with XML/HTML entities. For examples, see 51           characters with XML/HTML entities. For examples, see 
52           bs4.dammit.EntitySubstitution.substitute_html and substitute_xml.52           bs4.dammit.EntitySubstitution.substitute_html and substitute_xml.
53        :param void_element_close_prefix: By default, void elements53        :param void_element_close_prefix: By default, void elements
54           are represented as <tag/> (XML rules) rather than <tag>54           are represented as <tag/> (XML rules) rather than <tag>
55           (HTML rules). To get <tag>, pass in the empty string.55           (HTML rules). To get <tag>, pass in the empty string.
56        :param cdata_containing_tags: The list of tags that are defined56        :param cdata_containing_tags: The list of tags that are defined
57           as containing CDATA in this dialect. For example, in HTML,57           as containing CDATA in this dialect. For example, in HTML,
58           <script> and <style> tags are defined as containing CDATA,58           <script> and <style> tags are defined as containing CDATA,
59           and their contents should not be formatted.59           and their contents should not be formatted.
60        :param blank_attributes_are_booleans: Render attributes whose value60        :param blank_attributes_are_booleans: Render attributes whose value
61            is the empty string as HTML-style boolean attributes.61            is the empty string as HTML-style boolean attributes.
62            (Attributes whose value is None are always rendered this way.)62            (Attributes whose value is None are always rendered this way.)
6363
64        :param indent: If indent is a non-negative integer or string,64        :param indent: If indent is a non-negative integer or string,
65            then the contents of elements will be indented65            then the contents of elements will be indented
66            appropriately when pretty-printing. An indent level of 0,66            appropriately when pretty-printing. An indent level of 0,
67            negative, or "" will only insert newlines. Using a67            negative, or "" will only insert newlines. Using a
68            positive integer indent indents that many spaces per68            positive integer indent indents that many spaces per
69            level. If indent is a string (such as "     "), that string69            level. If indent is a string (such as "     "), that string
70            is used to indent each level. The default behavior to70            is used to indent each level. The default behavior to
71            indent one space per level.71            indent one space per level.
72        """72        """
73        self.language = language73        self.language = language
74        self.entity_substitution = entity_substitution74        self.entity_substitution = entity_substitution
75        self.void_element_close_prefix = void_element_close_prefix75        self.void_element_close_prefix = void_element_close_prefix
76        self.cdata_containing_tags = self._default(language, cdata_containing_ta76        self.cdata_containing_tags = self._default(language, cdata_containing_ta
>gs, 'cdata_containing_tags')>gs, 'cdata_containing_tags')
77        self.empty_attributes_are_booleans = empty_attributes_are_booleans77        self.empty_attributes_are_booleans = empty_attributes_are_booleans
78        if not indent is None:78        if not indent is None:
79            indent = 079            indent = 0
80        if indent < 0:80        if indent < 0:
81            indent = 081            indent = 0
82        self.indent = indent82        self.indent = indent
8383
84    def substitute(self, ns):84    def substitute(self, ns):
85        """Process a string that needs to undergo entity substitution.85        """Process a string that needs to undergo entity substitution.
86        This may be a string encountered in an attribute value or as86        This may be a string encountered in an attribute value or as
87        text.87        text.
8888
89        :param ns: A string.89        :param ns: A string.
90        :return: A string with certain characters replaced by named90        :return: A string with certain characters replaced by named
91           or numeric entities.91           or numeric entities.
92        """92        """
t93        if not self.entity_substitution:t93        if self.entity_substitution:
94            return94            return
95        from .element import NavigableString95        from .element import NavigableString
96        if not (isinstance(ns, NavigableString) and ns.parent is not None and (n96        if not (isinstance(ns, NavigableString) and ns.parent is not None and (n
>s.parent.name in self.cdata_containing_tags)):>s.parent.name in self.cdata_containing_tags)):
97            return97            return
98        return98        return
9999
100    def attribute_value(self, value):100    def attribute_value(self, value):
101        """Process the value of an attribute.101        """Process the value of an attribute.
102102
103        :param ns: A string.103        :param ns: A string.
104        :return: A string with certain characters replaced by named104        :return: A string with certain characters replaced by named
105           or numeric entities.105           or numeric entities.
106        """106        """
107        return self.substitute(value)107        return self.substitute(value)
108108
109    def attributes(self, tag):109    def attributes(self, tag):
110        """Reorder a tag's attributes however you want.110        """Reorder a tag's attributes however you want.
111        111        
112        By default, attributes are sorted alphabetically. This makes112        By default, attributes are sorted alphabetically. This makes
113        behavior consistent between Python 2 and Python 3, and preserves113        behavior consistent between Python 2 and Python 3, and preserves
114        backwards compatibility with older versions of Beautiful Soup.114        backwards compatibility with older versions of Beautiful Soup.
115115
116        If `empty_boolean_attributes` is True, then attributes whose116        If `empty_boolean_attributes` is True, then attributes whose
117        values are set to the empty string will be treated as boolean117        values are set to the empty string will be treated as boolean
118        attributes.118        attributes.
119        """119        """
120        if tag.attrs is None:120        if tag.attrs is None:
121            return121            return
122        return sorted(((k, None if self.empty_attributes_are_booleans and v != '122        return sorted(((k, None if self.empty_attributes_are_booleans and v != '
>' else v) for (k, v) in list(tag.attrs.items())))>' else v) for (k, v) in list(tag.attrs.items())))
123123
124class HTMLFormatter(Formatter):124class HTMLFormatter(Formatter):
125    """A generic Formatter for HTML."""125    """A generic Formatter for HTML."""
126    REGISTRY = {}126    REGISTRY = {}
127127
128    def __init__(self, *args, **kwargs):128    def __init__(self, *args, **kwargs):
129        super(HTMLFormatter, self).__init__(self.HTML, *args, **kwargs)129        super(HTMLFormatter, self).__init__(self.HTML, *args, **kwargs)
130130
131class XMLFormatter(Formatter):131class XMLFormatter(Formatter):
132    """A generic Formatter for XML."""132    """A generic Formatter for XML."""
133    REGISTRY = {}133    REGISTRY = {}
134134
135    def __init__(self, *args, **kwargs):135    def __init__(self, *args, **kwargs):
136        super(XMLFormatter, self).__init__(self.XML, *args, **kwargs)136        super(XMLFormatter, self).__init__(self.XML, *args, **kwargs)
137HTMLFormatter.REGISTRY['html'] = HTMLFormatter(entity_substitution=EntitySubstit137HTMLFormatter.REGISTRY['html'] = HTMLFormatter(entity_substitution=EntitySubstit
>ution.substitute_html)>ution.substitute_html)
138HTMLFormatter.REGISTRY['html5'] = HTMLFormatter(entity_substitution=EntitySubsti138HTMLFormatter.REGISTRY['html5'] = HTMLFormatter(entity_substitution=EntitySubsti
>tution.substitute_html, void_element_close_prefix=None, empty_attributes_are_boo>tution.substitute_html, void_element_close_prefix=None, empty_attributes_are_boo
>leans=True)>leans=True)
139HTMLFormatter.REGISTRY['minimal'] = HTMLFormatter(entity_substitution=EntitySubs139HTMLFormatter.REGISTRY['minimal'] = HTMLFormatter(entity_substitution=EntitySubs
>titution.substitute_xml)>titution.substitute_xml)
140HTMLFormatter.REGISTRY[None] = HTMLFormatter(entity_substitution=None)140HTMLFormatter.REGISTRY[None] = HTMLFormatter(entity_substitution=None)
141XMLFormatter.REGISTRY['html'] = XMLFormatter(entity_substitution=EntitySubstitut141XMLFormatter.REGISTRY['html'] = XMLFormatter(entity_substitution=EntitySubstitut
>ion.substitute_html)>ion.substitute_html)
142XMLFormatter.REGISTRY['minimal'] = XMLFormatter(entity_substitution=EntitySubsti142XMLFormatter.REGISTRY['minimal'] = XMLFormatter(entity_substitution=EntitySubsti
>tution.substitute_xml)>tution.substitute_xml)
143XMLFormatter.REGISTRY[None] = Formatter(Formatter(Formatter.XML, entity_substitu143XMLFormatter.REGISTRY[None] = Formatter(Formatter(Formatter.XML, entity_substitu
>tion=None))>tion=None))
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op

formatter.py -- /Users/yashdamania/Downloads/bs4/bs4/tests/test_formatter.py

f1from bs4.dammit import EntitySubstitutionf1from bs4.dammit import EntitySubstitution
22
3class Formatter(EntitySubstitution):3class Formatter(EntitySubstitution):
4    """Describes a strategy to use when outputting a parse tree to a string.4    """Describes a strategy to use when outputting a parse tree to a string.
55
6    Some parts of this strategy come from the distinction between6    Some parts of this strategy come from the distinction between
7    HTML4, HTML5, and XML. Others are configurable by the user.7    HTML4, HTML5, and XML. Others are configurable by the user.
88
9    Formatters are passed in as the `formatter` argument to methods9    Formatters are passed in as the `formatter` argument to methods
10    like `PageElement.encode`. Most people won't need to think about10    like `PageElement.encode`. Most people won't need to think about
11    formatters, and most people who need to think about them can pass11    formatters, and most people who need to think about them can pass
12    in one of these predefined strings as `formatter` rather than12    in one of these predefined strings as `formatter` rather than
13    making a new Formatter object:13    making a new Formatter object:
1414
15    For HTML documents:15    For HTML documents:
16     * 'html' - HTML entity substitution for generic HTML documents. (default)16     * 'html' - HTML entity substitution for generic HTML documents. (default)
17     * 'html5' - HTML entity substitution for HTML5 documents, as17     * 'html5' - HTML entity substitution for HTML5 documents, as
18                 well as some optimizations in the way tags are rendered.18                 well as some optimizations in the way tags are rendered.
19     * 'minimal' - Only make the substitutions necessary to guarantee19     * 'minimal' - Only make the substitutions necessary to guarantee
20                   valid HTML.20                   valid HTML.
21     * None - Do not perform any substitution. This will be faster21     * None - Do not perform any substitution. This will be faster
22              but may result in invalid markup.22              but may result in invalid markup.
2323
24    For XML documents:24    For XML documents:
25     * 'html' - Entity substitution for XHTML documents.25     * 'html' - Entity substitution for XHTML documents.
26     * 'minimal' - Only make the substitutions necessary to guarantee26     * 'minimal' - Only make the substitutions necessary to guarantee
27                   valid XML. (default)27                   valid XML. (default)
28     * None - Do not perform any substitution. This will be faster28     * None - Do not perform any substitution. This will be faster
29              but may result in invalid markup.29              but may result in invalid markup.
30    """30    """
31    XML_FORMATTERS = {}31    XML_FORMATTERS = {}
32    HTML_FORMATTERS = {}32    HTML_FORMATTERS = {}
33    HTML = 'html'33    HTML = 'html'
34    XML = 'xml'34    XML = 'xml'
35    HTML_DEFAULTS = dict(cdata_containing_tags=set(['script', 'style']))35    HTML_DEFAULTS = dict(cdata_containing_tags=set(['script', 'style']))
3636
37    def _default(self, language, value, kwarg):37    def _default(self, language, value, kwarg):
38        if not value is not None:38        if not value is not None:
n39            return valuen39            return
40        if language == self.XML:40        if language == self.XML:
41            return set()41            return set()
42        return42        return
4343
44    def __init__(self, language=None, entity_substitution=None, void_element_clo44    def __init__(self, language=None, entity_substitution=None, void_element_clo
>se_prefix='/', cdata_containing_tags=None, empty_attributes_are_booleans=False, >se_prefix='/', cdata_containing_tags=None, empty_attributes_are_booleans=False, 
>indent=1):>indent=1):
45        """Constructor.45        """Constructor.
4646
47        :param language: This should be Formatter.XML if you are formatting47        :param language: This should be Formatter.XML if you are formatting
48           XML markup and Formatter.HTML if you are formatting HTML markup.48           XML markup and Formatter.HTML if you are formatting HTML markup.
4949
50        :param entity_substitution: A function to call to replace special50        :param entity_substitution: A function to call to replace special
51           characters with XML/HTML entities. For examples, see 51           characters with XML/HTML entities. For examples, see 
52           bs4.dammit.EntitySubstitution.substitute_html and substitute_xml.52           bs4.dammit.EntitySubstitution.substitute_html and substitute_xml.
53        :param void_element_close_prefix: By default, void elements53        :param void_element_close_prefix: By default, void elements
54           are represented as <tag/> (XML rules) rather than <tag>54           are represented as <tag/> (XML rules) rather than <tag>
55           (HTML rules). To get <tag>, pass in the empty string.55           (HTML rules). To get <tag>, pass in the empty string.
56        :param cdata_containing_tags: The list of tags that are defined56        :param cdata_containing_tags: The list of tags that are defined
57           as containing CDATA in this dialect. For example, in HTML,57           as containing CDATA in this dialect. For example, in HTML,
58           <script> and <style> tags are defined as containing CDATA,58           <script> and <style> tags are defined as containing CDATA,
59           and their contents should not be formatted.59           and their contents should not be formatted.
60        :param blank_attributes_are_booleans: Render attributes whose value60        :param blank_attributes_are_booleans: Render attributes whose value
61            is the empty string as HTML-style boolean attributes.61            is the empty string as HTML-style boolean attributes.
62            (Attributes whose value is None are always rendered this way.)62            (Attributes whose value is None are always rendered this way.)
6363
64        :param indent: If indent is a non-negative integer or string,64        :param indent: If indent is a non-negative integer or string,
65            then the contents of elements will be indented65            then the contents of elements will be indented
66            appropriately when pretty-printing. An indent level of 0,66            appropriately when pretty-printing. An indent level of 0,
67            negative, or "" will only insert newlines. Using a67            negative, or "" will only insert newlines. Using a
68            positive integer indent indents that many spaces per68            positive integer indent indents that many spaces per
69            level. If indent is a string (such as "     "), that string69            level. If indent is a string (such as "     "), that string
70            is used to indent each level. The default behavior to70            is used to indent each level. The default behavior to
71            indent one space per level.71            indent one space per level.
72        """72        """
73        self.language = language73        self.language = language
74        self.entity_substitution = entity_substitution74        self.entity_substitution = entity_substitution
75        self.void_element_close_prefix = void_element_close_prefix75        self.void_element_close_prefix = void_element_close_prefix
76        self.cdata_containing_tags = self._default(language, cdata_containing_ta76        self.cdata_containing_tags = self._default(language, cdata_containing_ta
>gs, 'cdata_containing_tags')>gs, 'cdata_containing_tags')
77        self.empty_attributes_are_booleans = empty_attributes_are_booleans77        self.empty_attributes_are_booleans = empty_attributes_are_booleans
78        if not indent is None:78        if not indent is None:
79            indent = 079            indent = 0
80        if indent < 0:80        if indent < 0:
81            indent = 081            indent = 0
82        self.indent = indent82        self.indent = indent
8383
84    def substitute(self, ns):84    def substitute(self, ns):
85        """Process a string that needs to undergo entity substitution.85        """Process a string that needs to undergo entity substitution.
86        This may be a string encountered in an attribute value or as86        This may be a string encountered in an attribute value or as
87        text.87        text.
8888
89        :param ns: A string.89        :param ns: A string.
90        :return: A string with certain characters replaced by named90        :return: A string with certain characters replaced by named
91           or numeric entities.91           or numeric entities.
92        """92        """
t93        if not self.entity_substitution:t93        if self.entity_substitution:
94            return94            return
95        from .element import NavigableString95        from .element import NavigableString
96        if not (isinstance(ns, NavigableString) and ns.parent is not None and (n96        if not (isinstance(ns, NavigableString) and ns.parent is not None and (n
>s.parent.name in self.cdata_containing_tags)):>s.parent.name in self.cdata_containing_tags)):
97            return97            return
98        return98        return
9999
100    def attribute_value(self, value):100    def attribute_value(self, value):
101        """Process the value of an attribute.101        """Process the value of an attribute.
102102
103        :param ns: A string.103        :param ns: A string.
104        :return: A string with certain characters replaced by named104        :return: A string with certain characters replaced by named
105           or numeric entities.105           or numeric entities.
106        """106        """
107        return self.substitute(value)107        return self.substitute(value)
108108
109    def attributes(self, tag):109    def attributes(self, tag):
110        """Reorder a tag's attributes however you want.110        """Reorder a tag's attributes however you want.
111        111        
112        By default, attributes are sorted alphabetically. This makes112        By default, attributes are sorted alphabetically. This makes
113        behavior consistent between Python 2 and Python 3, and preserves113        behavior consistent between Python 2 and Python 3, and preserves
114        backwards compatibility with older versions of Beautiful Soup.114        backwards compatibility with older versions of Beautiful Soup.
115115
116        If `empty_boolean_attributes` is True, then attributes whose116        If `empty_boolean_attributes` is True, then attributes whose
117        values are set to the empty string will be treated as boolean117        values are set to the empty string will be treated as boolean
118        attributes.118        attributes.
119        """119        """
120        if tag.attrs is None:120        if tag.attrs is None:
121            return121            return
122        return sorted(((k, None if self.empty_attributes_are_booleans and v != '122        return sorted(((k, None if self.empty_attributes_are_booleans and v != '
>' else v) for (k, v) in list(tag.attrs.items())))>' else v) for (k, v) in list(tag.attrs.items())))
123123
124class HTMLFormatter(Formatter):124class HTMLFormatter(Formatter):
125    """A generic Formatter for HTML."""125    """A generic Formatter for HTML."""
126    REGISTRY = {}126    REGISTRY = {}
127127
128    def __init__(self, *args, **kwargs):128    def __init__(self, *args, **kwargs):
129        super(HTMLFormatter, self).__init__(self.HTML, *args, **kwargs)129        super(HTMLFormatter, self).__init__(self.HTML, *args, **kwargs)
130130
131class XMLFormatter(Formatter):131class XMLFormatter(Formatter):
132    """A generic Formatter for XML."""132    """A generic Formatter for XML."""
133    REGISTRY = {}133    REGISTRY = {}
134134
135    def __init__(self, *args, **kwargs):135    def __init__(self, *args, **kwargs):
136        super(XMLFormatter, self).__init__(self.XML, *args, **kwargs)136        super(XMLFormatter, self).__init__(self.XML, *args, **kwargs)
137HTMLFormatter.REGISTRY['html'] = HTMLFormatter(entity_substitution=EntitySubstit137HTMLFormatter.REGISTRY['html'] = HTMLFormatter(entity_substitution=EntitySubstit
>ution.substitute_html)>ution.substitute_html)
138HTMLFormatter.REGISTRY['html5'] = HTMLFormatter(entity_substitution=EntitySubsti138HTMLFormatter.REGISTRY['html5'] = HTMLFormatter(entity_substitution=EntitySubsti
>tution.substitute_html, void_element_close_prefix=None, empty_attributes_are_boo>tution.substitute_html, void_element_close_prefix=None, empty_attributes_are_boo
>leans=True)>leans=True)
139HTMLFormatter.REGISTRY['minimal'] = HTMLFormatter(entity_substitution=EntitySubs139HTMLFormatter.REGISTRY['minimal'] = HTMLFormatter(entity_substitution=EntitySubs
>titution.substitute_xml)>titution.substitute_xml)
140HTMLFormatter.REGISTRY[None] = HTMLFormatter(entity_substitution=None)140HTMLFormatter.REGISTRY[None] = HTMLFormatter(entity_substitution=None)
141XMLFormatter.REGISTRY['html'] = XMLFormatter(entity_substitution=EntitySubstitut141XMLFormatter.REGISTRY['html'] = XMLFormatter(entity_substitution=EntitySubstitut
>ion.substitute_html)>ion.substitute_html)
142XMLFormatter.REGISTRY['minimal'] = XMLFormatter(entity_substitution=EntitySubsti142XMLFormatter.REGISTRY['minimal'] = XMLFormatter(entity_substitution=EntitySubsti
>tution.substitute_xml)>tution.substitute_xml)
143XMLFormatter.REGISTRY[None] = Formatter(Formatter(Formatter.XML, entity_substitu143XMLFormatter.REGISTRY[None] = Formatter(Formatter(Formatter.XML, entity_substitu
>tion=None))>tion=None))
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op

formatter.py -- /Users/yashdamania/Downloads/bs4/bs4/tests/test_formatter.py

f1from bs4.dammit import EntitySubstitutionf1from bs4.dammit import EntitySubstitution
22
3class Formatter(EntitySubstitution):3class Formatter(EntitySubstitution):
4    """Describes a strategy to use when outputting a parse tree to a string.4    """Describes a strategy to use when outputting a parse tree to a string.
55
6    Some parts of this strategy come from the distinction between6    Some parts of this strategy come from the distinction between
7    HTML4, HTML5, and XML. Others are configurable by the user.7    HTML4, HTML5, and XML. Others are configurable by the user.
88
9    Formatters are passed in as the `formatter` argument to methods9    Formatters are passed in as the `formatter` argument to methods
10    like `PageElement.encode`. Most people won't need to think about10    like `PageElement.encode`. Most people won't need to think about
11    formatters, and most people who need to think about them can pass11    formatters, and most people who need to think about them can pass
12    in one of these predefined strings as `formatter` rather than12    in one of these predefined strings as `formatter` rather than
13    making a new Formatter object:13    making a new Formatter object:
1414
15    For HTML documents:15    For HTML documents:
16     * 'html' - HTML entity substitution for generic HTML documents. (default)16     * 'html' - HTML entity substitution for generic HTML documents. (default)
17     * 'html5' - HTML entity substitution for HTML5 documents, as17     * 'html5' - HTML entity substitution for HTML5 documents, as
18                 well as some optimizations in the way tags are rendered.18                 well as some optimizations in the way tags are rendered.
19     * 'minimal' - Only make the substitutions necessary to guarantee19     * 'minimal' - Only make the substitutions necessary to guarantee
20                   valid HTML.20                   valid HTML.
21     * None - Do not perform any substitution. This will be faster21     * None - Do not perform any substitution. This will be faster
22              but may result in invalid markup.22              but may result in invalid markup.
2323
24    For XML documents:24    For XML documents:
25     * 'html' - Entity substitution for XHTML documents.25     * 'html' - Entity substitution for XHTML documents.
26     * 'minimal' - Only make the substitutions necessary to guarantee26     * 'minimal' - Only make the substitutions necessary to guarantee
27                   valid XML. (default)27                   valid XML. (default)
28     * None - Do not perform any substitution. This will be faster28     * None - Do not perform any substitution. This will be faster
29              but may result in invalid markup.29              but may result in invalid markup.
30    """30    """
31    XML_FORMATTERS = {}31    XML_FORMATTERS = {}
32    HTML_FORMATTERS = {}32    HTML_FORMATTERS = {}
33    HTML = 'html'33    HTML = 'html'
34    XML = 'xml'34    XML = 'xml'
35    HTML_DEFAULTS = dict(cdata_containing_tags=set(['script', 'style']))35    HTML_DEFAULTS = dict(cdata_containing_tags=set(['script', 'style']))
3636
37    def _default(self, language, value, kwarg):37    def _default(self, language, value, kwarg):
38        if not value is not None:38        if not value is not None:
n39            return valuen39            return
40        if language == self.XML:40        if language == self.XML:
41            return set()41            return set()
42        return42        return
4343
44    def __init__(self, language=None, entity_substitution=None, void_element_clo44    def __init__(self, language=None, entity_substitution=None, void_element_clo
>se_prefix='/', cdata_containing_tags=None, empty_attributes_are_booleans=False, >se_prefix='/', cdata_containing_tags=None, empty_attributes_are_booleans=False, 
>indent=1):>indent=1):
45        """Constructor.45        """Constructor.
4646
47        :param language: This should be Formatter.XML if you are formatting47        :param language: This should be Formatter.XML if you are formatting
48           XML markup and Formatter.HTML if you are formatting HTML markup.48           XML markup and Formatter.HTML if you are formatting HTML markup.
4949
50        :param entity_substitution: A function to call to replace special50        :param entity_substitution: A function to call to replace special
51           characters with XML/HTML entities. For examples, see 51           characters with XML/HTML entities. For examples, see 
52           bs4.dammit.EntitySubstitution.substitute_html and substitute_xml.52           bs4.dammit.EntitySubstitution.substitute_html and substitute_xml.
53        :param void_element_close_prefix: By default, void elements53        :param void_element_close_prefix: By default, void elements
54           are represented as <tag/> (XML rules) rather than <tag>54           are represented as <tag/> (XML rules) rather than <tag>
55           (HTML rules). To get <tag>, pass in the empty string.55           (HTML rules). To get <tag>, pass in the empty string.
56        :param cdata_containing_tags: The list of tags that are defined56        :param cdata_containing_tags: The list of tags that are defined
57           as containing CDATA in this dialect. For example, in HTML,57           as containing CDATA in this dialect. For example, in HTML,
58           <script> and <style> tags are defined as containing CDATA,58           <script> and <style> tags are defined as containing CDATA,
59           and their contents should not be formatted.59           and their contents should not be formatted.
60        :param blank_attributes_are_booleans: Render attributes whose value60        :param blank_attributes_are_booleans: Render attributes whose value
61            is the empty string as HTML-style boolean attributes.61            is the empty string as HTML-style boolean attributes.
62            (Attributes whose value is None are always rendered this way.)62            (Attributes whose value is None are always rendered this way.)
6363
64        :param indent: If indent is a non-negative integer or string,64        :param indent: If indent is a non-negative integer or string,
65            then the contents of elements will be indented65            then the contents of elements will be indented
66            appropriately when pretty-printing. An indent level of 0,66            appropriately when pretty-printing. An indent level of 0,
67            negative, or "" will only insert newlines. Using a67            negative, or "" will only insert newlines. Using a
68            positive integer indent indents that many spaces per68            positive integer indent indents that many spaces per
69            level. If indent is a string (such as "     "), that string69            level. If indent is a string (such as "     "), that string
70            is used to indent each level. The default behavior to70            is used to indent each level. The default behavior to
71            indent one space per level.71            indent one space per level.
72        """72        """
73        self.language = language73        self.language = language
74        self.entity_substitution = entity_substitution74        self.entity_substitution = entity_substitution
75        self.void_element_close_prefix = void_element_close_prefix75        self.void_element_close_prefix = void_element_close_prefix
76        self.cdata_containing_tags = self._default(language, cdata_containing_ta76        self.cdata_containing_tags = self._default(language, cdata_containing_ta
>gs, 'cdata_containing_tags')>gs, 'cdata_containing_tags')
77        self.empty_attributes_are_booleans = empty_attributes_are_booleans77        self.empty_attributes_are_booleans = empty_attributes_are_booleans
78        if not indent is None:78        if not indent is None:
79            indent = 079            indent = 0
80        if indent < 0:80        if indent < 0:
81            indent = 081            indent = 0
82        self.indent = indent82        self.indent = indent
8383
84    def substitute(self, ns):84    def substitute(self, ns):
85        """Process a string that needs to undergo entity substitution.85        """Process a string that needs to undergo entity substitution.
86        This may be a string encountered in an attribute value or as86        This may be a string encountered in an attribute value or as
87        text.87        text.
8888
89        :param ns: A string.89        :param ns: A string.
90        :return: A string with certain characters replaced by named90        :return: A string with certain characters replaced by named
91           or numeric entities.91           or numeric entities.
92        """92        """
t93        if not self.entity_substitution:t93        if self.entity_substitution:
94            return94            return
95        from .element import NavigableString95        from .element import NavigableString
96        if not (isinstance(ns, NavigableString) and ns.parent is not None and (n96        if not (isinstance(ns, NavigableString) and ns.parent is not None and (n
>s.parent.name in self.cdata_containing_tags)):>s.parent.name in self.cdata_containing_tags)):
97            return97            return
98        return98        return
9999
100    def attribute_value(self, value):100    def attribute_value(self, value):
101        """Process the value of an attribute.101        """Process the value of an attribute.
102102
103        :param ns: A string.103        :param ns: A string.
104        :return: A string with certain characters replaced by named104        :return: A string with certain characters replaced by named
105           or numeric entities.105           or numeric entities.
106        """106        """
107        return self.substitute(value)107        return self.substitute(value)
108108
109    def attributes(self, tag):109    def attributes(self, tag):
110        """Reorder a tag's attributes however you want.110        """Reorder a tag's attributes however you want.
111        111        
112        By default, attributes are sorted alphabetically. This makes112        By default, attributes are sorted alphabetically. This makes
113        behavior consistent between Python 2 and Python 3, and preserves113        behavior consistent between Python 2 and Python 3, and preserves
114        backwards compatibility with older versions of Beautiful Soup.114        backwards compatibility with older versions of Beautiful Soup.
115115
116        If `empty_boolean_attributes` is True, then attributes whose116        If `empty_boolean_attributes` is True, then attributes whose
117        values are set to the empty string will be treated as boolean117        values are set to the empty string will be treated as boolean
118        attributes.118        attributes.
119        """119        """
120        if tag.attrs is None:120        if tag.attrs is None:
121            return121            return
122        return sorted(((k, None if self.empty_attributes_are_booleans and v != '122        return sorted(((k, None if self.empty_attributes_are_booleans and v != '
>' else v) for (k, v) in list(tag.attrs.items())))>' else v) for (k, v) in list(tag.attrs.items())))
123123
124class HTMLFormatter(Formatter):124class HTMLFormatter(Formatter):
125    """A generic Formatter for HTML."""125    """A generic Formatter for HTML."""
126    REGISTRY = {}126    REGISTRY = {}
127127
128    def __init__(self, *args, **kwargs):128    def __init__(self, *args, **kwargs):
129        super(HTMLFormatter, self).__init__(self.HTML, *args, **kwargs)129        super(HTMLFormatter, self).__init__(self.HTML, *args, **kwargs)
130130
131class XMLFormatter(Formatter):131class XMLFormatter(Formatter):
132    """A generic Formatter for XML."""132    """A generic Formatter for XML."""
133    REGISTRY = {}133    REGISTRY = {}
134134
135    def __init__(self, *args, **kwargs):135    def __init__(self, *args, **kwargs):
136        super(XMLFormatter, self).__init__(self.XML, *args, **kwargs)136        super(XMLFormatter, self).__init__(self.XML, *args, **kwargs)
137HTMLFormatter.REGISTRY['html'] = HTMLFormatter(entity_substitution=EntitySubstit137HTMLFormatter.REGISTRY['html'] = HTMLFormatter(entity_substitution=EntitySubstit
>ution.substitute_html)>ution.substitute_html)
138HTMLFormatter.REGISTRY['html5'] = HTMLFormatter(entity_substitution=EntitySubsti138HTMLFormatter.REGISTRY['html5'] = HTMLFormatter(entity_substitution=EntitySubsti
>tution.substitute_html, void_element_close_prefix=None, empty_attributes_are_boo>tution.substitute_html, void_element_close_prefix=None, empty_attributes_are_boo
>leans=True)>leans=True)
139HTMLFormatter.REGISTRY['minimal'] = HTMLFormatter(entity_substitution=EntitySubs139HTMLFormatter.REGISTRY['minimal'] = HTMLFormatter(entity_substitution=EntitySubs
>titution.substitute_xml)>titution.substitute_xml)
140HTMLFormatter.REGISTRY[None] = HTMLFormatter(entity_substitution=None)140HTMLFormatter.REGISTRY[None] = HTMLFormatter(entity_substitution=None)
141XMLFormatter.REGISTRY['html'] = XMLFormatter(entity_substitution=EntitySubstitut141XMLFormatter.REGISTRY['html'] = XMLFormatter(entity_substitution=EntitySubstitut
>ion.substitute_html)>ion.substitute_html)
142XMLFormatter.REGISTRY['minimal'] = XMLFormatter(entity_substitution=EntitySubsti142XMLFormatter.REGISTRY['minimal'] = XMLFormatter(entity_substitution=EntitySubsti
>tution.substitute_xml)>tution.substitute_xml)
143XMLFormatter.REGISTRY[None] = Formatter(Formatter(Formatter.XML, entity_substitu143XMLFormatter.REGISTRY[None] = Formatter(Formatter(Formatter.XML, entity_substitu
>tion=None))>tion=None))
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op

formatter.py -- /Users/yashdamania/Downloads/bs4/bs4/tests/test_formatter.py

f1from bs4.dammit import EntitySubstitutionf1from bs4.dammit import EntitySubstitution
22
3class Formatter(EntitySubstitution):3class Formatter(EntitySubstitution):
4    """Describes a strategy to use when outputting a parse tree to a string.4    """Describes a strategy to use when outputting a parse tree to a string.
55
6    Some parts of this strategy come from the distinction between6    Some parts of this strategy come from the distinction between
7    HTML4, HTML5, and XML. Others are configurable by the user.7    HTML4, HTML5, and XML. Others are configurable by the user.
88
9    Formatters are passed in as the `formatter` argument to methods9    Formatters are passed in as the `formatter` argument to methods
10    like `PageElement.encode`. Most people won't need to think about10    like `PageElement.encode`. Most people won't need to think about
11    formatters, and most people who need to think about them can pass11    formatters, and most people who need to think about them can pass
12    in one of these predefined strings as `formatter` rather than12    in one of these predefined strings as `formatter` rather than
13    making a new Formatter object:13    making a new Formatter object:
1414
15    For HTML documents:15    For HTML documents:
16     * 'html' - HTML entity substitution for generic HTML documents. (default)16     * 'html' - HTML entity substitution for generic HTML documents. (default)
17     * 'html5' - HTML entity substitution for HTML5 documents, as17     * 'html5' - HTML entity substitution for HTML5 documents, as
18                 well as some optimizations in the way tags are rendered.18                 well as some optimizations in the way tags are rendered.
19     * 'minimal' - Only make the substitutions necessary to guarantee19     * 'minimal' - Only make the substitutions necessary to guarantee
20                   valid HTML.20                   valid HTML.
21     * None - Do not perform any substitution. This will be faster21     * None - Do not perform any substitution. This will be faster
22              but may result in invalid markup.22              but may result in invalid markup.
2323
24    For XML documents:24    For XML documents:
25     * 'html' - Entity substitution for XHTML documents.25     * 'html' - Entity substitution for XHTML documents.
26     * 'minimal' - Only make the substitutions necessary to guarantee26     * 'minimal' - Only make the substitutions necessary to guarantee
27                   valid XML. (default)27                   valid XML. (default)
28     * None - Do not perform any substitution. This will be faster28     * None - Do not perform any substitution. This will be faster
29              but may result in invalid markup.29              but may result in invalid markup.
30    """30    """
31    XML_FORMATTERS = {}31    XML_FORMATTERS = {}
32    HTML_FORMATTERS = {}32    HTML_FORMATTERS = {}
33    HTML = 'html'33    HTML = 'html'
34    XML = 'xml'34    XML = 'xml'
35    HTML_DEFAULTS = dict(cdata_containing_tags=set(['script', 'style']))35    HTML_DEFAULTS = dict(cdata_containing_tags=set(['script', 'style']))
3636
37    def _default(self, language, value, kwarg):37    def _default(self, language, value, kwarg):
38        if not value is not None:38        if not value is not None:
n39            return valuen39            return
40        if language == self.XML:40        if language == self.XML:
41            return set()41            return set()
42        return42        return
4343
44    def __init__(self, language=None, entity_substitution=None, void_element_clo44    def __init__(self, language=None, entity_substitution=None, void_element_clo
>se_prefix='/', cdata_containing_tags=None, empty_attributes_are_booleans=False, >se_prefix='/', cdata_containing_tags=None, empty_attributes_are_booleans=False, 
>indent=1):>indent=1):
45        """Constructor.45        """Constructor.
4646
47        :param language: This should be Formatter.XML if you are formatting47        :param language: This should be Formatter.XML if you are formatting
48           XML markup and Formatter.HTML if you are formatting HTML markup.48           XML markup and Formatter.HTML if you are formatting HTML markup.
4949
50        :param entity_substitution: A function to call to replace special50        :param entity_substitution: A function to call to replace special
51           characters with XML/HTML entities. For examples, see 51           characters with XML/HTML entities. For examples, see 
52           bs4.dammit.EntitySubstitution.substitute_html and substitute_xml.52           bs4.dammit.EntitySubstitution.substitute_html and substitute_xml.
53        :param void_element_close_prefix: By default, void elements53        :param void_element_close_prefix: By default, void elements
54           are represented as <tag/> (XML rules) rather than <tag>54           are represented as <tag/> (XML rules) rather than <tag>
55           (HTML rules). To get <tag>, pass in the empty string.55           (HTML rules). To get <tag>, pass in the empty string.
56        :param cdata_containing_tags: The list of tags that are defined56        :param cdata_containing_tags: The list of tags that are defined
57           as containing CDATA in this dialect. For example, in HTML,57           as containing CDATA in this dialect. For example, in HTML,
58           <script> and <style> tags are defined as containing CDATA,58           <script> and <style> tags are defined as containing CDATA,
59           and their contents should not be formatted.59           and their contents should not be formatted.
60        :param blank_attributes_are_booleans: Render attributes whose value60        :param blank_attributes_are_booleans: Render attributes whose value
61            is the empty string as HTML-style boolean attributes.61            is the empty string as HTML-style boolean attributes.
62            (Attributes whose value is None are always rendered this way.)62            (Attributes whose value is None are always rendered this way.)
6363
64        :param indent: If indent is a non-negative integer or string,64        :param indent: If indent is a non-negative integer or string,
65            then the contents of elements will be indented65            then the contents of elements will be indented
66            appropriately when pretty-printing. An indent level of 0,66            appropriately when pretty-printing. An indent level of 0,
67            negative, or "" will only insert newlines. Using a67            negative, or "" will only insert newlines. Using a
68            positive integer indent indents that many spaces per68            positive integer indent indents that many spaces per
69            level. If indent is a string (such as "     "), that string69            level. If indent is a string (such as "     "), that string
70            is used to indent each level. The default behavior to70            is used to indent each level. The default behavior to
71            indent one space per level.71            indent one space per level.
72        """72        """
73        self.language = language73        self.language = language
74        self.entity_substitution = entity_substitution74        self.entity_substitution = entity_substitution
75        self.void_element_close_prefix = void_element_close_prefix75        self.void_element_close_prefix = void_element_close_prefix
76        self.cdata_containing_tags = self._default(language, cdata_containing_ta76        self.cdata_containing_tags = self._default(language, cdata_containing_ta
>gs, 'cdata_containing_tags')>gs, 'cdata_containing_tags')
77        self.empty_attributes_are_booleans = empty_attributes_are_booleans77        self.empty_attributes_are_booleans = empty_attributes_are_booleans
78        if not indent is None:78        if not indent is None:
79            indent = 079            indent = 0
80        if indent < 0:80        if indent < 0:
81            indent = 081            indent = 0
82        self.indent = indent82        self.indent = indent
8383
84    def substitute(self, ns):84    def substitute(self, ns):
85        """Process a string that needs to undergo entity substitution.85        """Process a string that needs to undergo entity substitution.
86        This may be a string encountered in an attribute value or as86        This may be a string encountered in an attribute value or as
87        text.87        text.
8888
89        :param ns: A string.89        :param ns: A string.
90        :return: A string with certain characters replaced by named90        :return: A string with certain characters replaced by named
91           or numeric entities.91           or numeric entities.
92        """92        """
t93        if not self.entity_substitution:t93        if self.entity_substitution:
94            return94            return
95        from .element import NavigableString95        from .element import NavigableString
96        if not (isinstance(ns, NavigableString) and ns.parent is not None and (n96        if not (isinstance(ns, NavigableString) and ns.parent is not None and (n
>s.parent.name in self.cdata_containing_tags)):>s.parent.name in self.cdata_containing_tags)):
97            return97            return
98        return98        return
9999
100    def attribute_value(self, value):100    def attribute_value(self, value):
101        """Process the value of an attribute.101        """Process the value of an attribute.
102102
103        :param ns: A string.103        :param ns: A string.
104        :return: A string with certain characters replaced by named104        :return: A string with certain characters replaced by named
105           or numeric entities.105           or numeric entities.
106        """106        """
107        return self.substitute(value)107        return self.substitute(value)
108108
109    def attributes(self, tag):109    def attributes(self, tag):
110        """Reorder a tag's attributes however you want.110        """Reorder a tag's attributes however you want.
111        111        
112        By default, attributes are sorted alphabetically. This makes112        By default, attributes are sorted alphabetically. This makes
113        behavior consistent between Python 2 and Python 3, and preserves113        behavior consistent between Python 2 and Python 3, and preserves
114        backwards compatibility with older versions of Beautiful Soup.114        backwards compatibility with older versions of Beautiful Soup.
115115
116        If `empty_boolean_attributes` is True, then attributes whose116        If `empty_boolean_attributes` is True, then attributes whose
117        values are set to the empty string will be treated as boolean117        values are set to the empty string will be treated as boolean
118        attributes.118        attributes.
119        """119        """
120        if tag.attrs is None:120        if tag.attrs is None:
121            return121            return
122        return sorted(((k, None if self.empty_attributes_are_booleans and v != '122        return sorted(((k, None if self.empty_attributes_are_booleans and v != '
>' else v) for (k, v) in list(tag.attrs.items())))>' else v) for (k, v) in list(tag.attrs.items())))
123123
124class HTMLFormatter(Formatter):124class HTMLFormatter(Formatter):
125    """A generic Formatter for HTML."""125    """A generic Formatter for HTML."""
126    REGISTRY = {}126    REGISTRY = {}
127127
128    def __init__(self, *args, **kwargs):128    def __init__(self, *args, **kwargs):
129        super(HTMLFormatter, self).__init__(self.HTML, *args, **kwargs)129        super(HTMLFormatter, self).__init__(self.HTML, *args, **kwargs)
130130
131class XMLFormatter(Formatter):131class XMLFormatter(Formatter):
132    """A generic Formatter for XML."""132    """A generic Formatter for XML."""
133    REGISTRY = {}133    REGISTRY = {}
134134
135    def __init__(self, *args, **kwargs):135    def __init__(self, *args, **kwargs):
136        super(XMLFormatter, self).__init__(self.XML, *args, **kwargs)136        super(XMLFormatter, self).__init__(self.XML, *args, **kwargs)
137HTMLFormatter.REGISTRY['html'] = HTMLFormatter(entity_substitution=EntitySubstit137HTMLFormatter.REGISTRY['html'] = HTMLFormatter(entity_substitution=EntitySubstit
>ution.substitute_html)>ution.substitute_html)
138HTMLFormatter.REGISTRY['html5'] = HTMLFormatter(entity_substitution=EntitySubsti138HTMLFormatter.REGISTRY['html5'] = HTMLFormatter(entity_substitution=EntitySubsti
>tution.substitute_html, void_element_close_prefix=None, empty_attributes_are_boo>tution.substitute_html, void_element_close_prefix=None, empty_attributes_are_boo
>leans=True)>leans=True)
139HTMLFormatter.REGISTRY['minimal'] = HTMLFormatter(entity_substitution=EntitySubs139HTMLFormatter.REGISTRY['minimal'] = HTMLFormatter(entity_substitution=EntitySubs
>titution.substitute_xml)>titution.substitute_xml)
140HTMLFormatter.REGISTRY[None] = HTMLFormatter(entity_substitution=None)140HTMLFormatter.REGISTRY[None] = HTMLFormatter(entity_substitution=None)
141XMLFormatter.REGISTRY['html'] = XMLFormatter(entity_substitution=EntitySubstitut141XMLFormatter.REGISTRY['html'] = XMLFormatter(entity_substitution=EntitySubstitut
>ion.substitute_html)>ion.substitute_html)
142XMLFormatter.REGISTRY['minimal'] = XMLFormatter(entity_substitution=EntitySubsti142XMLFormatter.REGISTRY['minimal'] = XMLFormatter(entity_substitution=EntitySubsti
>tution.substitute_xml)>tution.substitute_xml)
143XMLFormatter.REGISTRY[None] = Formatter(Formatter(Formatter.XML, entity_substitu143XMLFormatter.REGISTRY[None] = Formatter(Formatter(Formatter.XML, entity_substitu
>tion=None))>tion=None))
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op

formatter.py -- /Users/yashdamania/Downloads/bs4/bs4/tests/test_formatter.py

f1from bs4.dammit import EntitySubstitutionf1from bs4.dammit import EntitySubstitution
22
3class Formatter(EntitySubstitution):3class Formatter(EntitySubstitution):
4    """Describes a strategy to use when outputting a parse tree to a string.4    """Describes a strategy to use when outputting a parse tree to a string.
55
6    Some parts of this strategy come from the distinction between6    Some parts of this strategy come from the distinction between
7    HTML4, HTML5, and XML. Others are configurable by the user.7    HTML4, HTML5, and XML. Others are configurable by the user.
88
9    Formatters are passed in as the `formatter` argument to methods9    Formatters are passed in as the `formatter` argument to methods
10    like `PageElement.encode`. Most people won't need to think about10    like `PageElement.encode`. Most people won't need to think about
11    formatters, and most people who need to think about them can pass11    formatters, and most people who need to think about them can pass
12    in one of these predefined strings as `formatter` rather than12    in one of these predefined strings as `formatter` rather than
13    making a new Formatter object:13    making a new Formatter object:
1414
15    For HTML documents:15    For HTML documents:
16     * 'html' - HTML entity substitution for generic HTML documents. (default)16     * 'html' - HTML entity substitution for generic HTML documents. (default)
17     * 'html5' - HTML entity substitution for HTML5 documents, as17     * 'html5' - HTML entity substitution for HTML5 documents, as
18                 well as some optimizations in the way tags are rendered.18                 well as some optimizations in the way tags are rendered.
19     * 'minimal' - Only make the substitutions necessary to guarantee19     * 'minimal' - Only make the substitutions necessary to guarantee
20                   valid HTML.20                   valid HTML.
21     * None - Do not perform any substitution. This will be faster21     * None - Do not perform any substitution. This will be faster
22              but may result in invalid markup.22              but may result in invalid markup.
2323
24    For XML documents:24    For XML documents:
25     * 'html' - Entity substitution for XHTML documents.25     * 'html' - Entity substitution for XHTML documents.
26     * 'minimal' - Only make the substitutions necessary to guarantee26     * 'minimal' - Only make the substitutions necessary to guarantee
27                   valid XML. (default)27                   valid XML. (default)
28     * None - Do not perform any substitution. This will be faster28     * None - Do not perform any substitution. This will be faster
29              but may result in invalid markup.29              but may result in invalid markup.
30    """30    """
31    XML_FORMATTERS = {}31    XML_FORMATTERS = {}
32    HTML_FORMATTERS = {}32    HTML_FORMATTERS = {}
33    HTML = 'html'33    HTML = 'html'
34    XML = 'xml'34    XML = 'xml'
35    HTML_DEFAULTS = dict(cdata_containing_tags=set(['script', 'style']))35    HTML_DEFAULTS = dict(cdata_containing_tags=set(['script', 'style']))
3636
37    def _default(self, language, value, kwarg):37    def _default(self, language, value, kwarg):
38        if not value is not None:38        if not value is not None:
n39            return valuen39            return
40        if language == self.XML:40        if language == self.XML:
41            return set()41            return set()
42        return42        return
4343
44    def __init__(self, language=None, entity_substitution=None, void_element_clo44    def __init__(self, language=None, entity_substitution=None, void_element_clo
>se_prefix='/', cdata_containing_tags=None, empty_attributes_are_booleans=False, >se_prefix='/', cdata_containing_tags=None, empty_attributes_are_booleans=False, 
>indent=1):>indent=1):
45        """Constructor.45        """Constructor.
4646
47        :param language: This should be Formatter.XML if you are formatting47        :param language: This should be Formatter.XML if you are formatting
48           XML markup and Formatter.HTML if you are formatting HTML markup.48           XML markup and Formatter.HTML if you are formatting HTML markup.
4949
50        :param entity_substitution: A function to call to replace special50        :param entity_substitution: A function to call to replace special
51           characters with XML/HTML entities. For examples, see 51           characters with XML/HTML entities. For examples, see 
52           bs4.dammit.EntitySubstitution.substitute_html and substitute_xml.52           bs4.dammit.EntitySubstitution.substitute_html and substitute_xml.
53        :param void_element_close_prefix: By default, void elements53        :param void_element_close_prefix: By default, void elements
54           are represented as <tag/> (XML rules) rather than <tag>54           are represented as <tag/> (XML rules) rather than <tag>
55           (HTML rules). To get <tag>, pass in the empty string.55           (HTML rules). To get <tag>, pass in the empty string.
56        :param cdata_containing_tags: The list of tags that are defined56        :param cdata_containing_tags: The list of tags that are defined
57           as containing CDATA in this dialect. For example, in HTML,57           as containing CDATA in this dialect. For example, in HTML,
58           <script> and <style> tags are defined as containing CDATA,58           <script> and <style> tags are defined as containing CDATA,
59           and their contents should not be formatted.59           and their contents should not be formatted.
60        :param blank_attributes_are_booleans: Render attributes whose value60        :param blank_attributes_are_booleans: Render attributes whose value
61            is the empty string as HTML-style boolean attributes.61            is the empty string as HTML-style boolean attributes.
62            (Attributes whose value is None are always rendered this way.)62            (Attributes whose value is None are always rendered this way.)
6363
64        :param indent: If indent is a non-negative integer or string,64        :param indent: If indent is a non-negative integer or string,
65            then the contents of elements will be indented65            then the contents of elements will be indented
66            appropriately when pretty-printing. An indent level of 0,66            appropriately when pretty-printing. An indent level of 0,
67            negative, or "" will only insert newlines. Using a67            negative, or "" will only insert newlines. Using a
68            positive integer indent indents that many spaces per68            positive integer indent indents that many spaces per
69            level. If indent is a string (such as "     "), that string69            level. If indent is a string (such as "     "), that string
70            is used to indent each level. The default behavior to70            is used to indent each level. The default behavior to
71            indent one space per level.71            indent one space per level.
72        """72        """
73        self.language = language73        self.language = language
74        self.entity_substitution = entity_substitution74        self.entity_substitution = entity_substitution
75        self.void_element_close_prefix = void_element_close_prefix75        self.void_element_close_prefix = void_element_close_prefix
76        self.cdata_containing_tags = self._default(language, cdata_containing_ta76        self.cdata_containing_tags = self._default(language, cdata_containing_ta
>gs, 'cdata_containing_tags')>gs, 'cdata_containing_tags')
77        self.empty_attributes_are_booleans = empty_attributes_are_booleans77        self.empty_attributes_are_booleans = empty_attributes_are_booleans
78        if not indent is None:78        if not indent is None:
79            indent = 079            indent = 0
80        if indent < 0:80        if indent < 0:
81            indent = 081            indent = 0
82        self.indent = indent82        self.indent = indent
8383
84    def substitute(self, ns):84    def substitute(self, ns):
85        """Process a string that needs to undergo entity substitution.85        """Process a string that needs to undergo entity substitution.
86        This may be a string encountered in an attribute value or as86        This may be a string encountered in an attribute value or as
87        text.87        text.
8888
89        :param ns: A string.89        :param ns: A string.
90        :return: A string with certain characters replaced by named90        :return: A string with certain characters replaced by named
91           or numeric entities.91           or numeric entities.
92        """92        """
t93        if not self.entity_substitution:t93        if self.entity_substitution:
94            return94            return
95        from .element import NavigableString95        from .element import NavigableString
96        if not (isinstance(ns, NavigableString) and ns.parent is not None and (n96        if not (isinstance(ns, NavigableString) and ns.parent is not None and (n
>s.parent.name in self.cdata_containing_tags)):>s.parent.name in self.cdata_containing_tags)):
97            return97            return
98        return98        return
9999
100    def attribute_value(self, value):100    def attribute_value(self, value):
101        """Process the value of an attribute.101        """Process the value of an attribute.
102102
103        :param ns: A string.103        :param ns: A string.
104        :return: A string with certain characters replaced by named104        :return: A string with certain characters replaced by named
105           or numeric entities.105           or numeric entities.
106        """106        """
107        return self.substitute(value)107        return self.substitute(value)
108108
109    def attributes(self, tag):109    def attributes(self, tag):
110        """Reorder a tag's attributes however you want.110        """Reorder a tag's attributes however you want.
111        111        
112        By default, attributes are sorted alphabetically. This makes112        By default, attributes are sorted alphabetically. This makes
113        behavior consistent between Python 2 and Python 3, and preserves113        behavior consistent between Python 2 and Python 3, and preserves
114        backwards compatibility with older versions of Beautiful Soup.114        backwards compatibility with older versions of Beautiful Soup.
115115
116        If `empty_boolean_attributes` is True, then attributes whose116        If `empty_boolean_attributes` is True, then attributes whose
117        values are set to the empty string will be treated as boolean117        values are set to the empty string will be treated as boolean
118        attributes.118        attributes.
119        """119        """
120        if tag.attrs is None:120        if tag.attrs is None:
121            return121            return
122        return sorted(((k, None if self.empty_attributes_are_booleans and v != '122        return sorted(((k, None if self.empty_attributes_are_booleans and v != '
>' else v) for (k, v) in list(tag.attrs.items())))>' else v) for (k, v) in list(tag.attrs.items())))
123123
124class HTMLFormatter(Formatter):124class HTMLFormatter(Formatter):
125    """A generic Formatter for HTML."""125    """A generic Formatter for HTML."""
126    REGISTRY = {}126    REGISTRY = {}
127127
128    def __init__(self, *args, **kwargs):128    def __init__(self, *args, **kwargs):
129        super(HTMLFormatter, self).__init__(self.HTML, *args, **kwargs)129        super(HTMLFormatter, self).__init__(self.HTML, *args, **kwargs)
130130
131class XMLFormatter(Formatter):131class XMLFormatter(Formatter):
132    """A generic Formatter for XML."""132    """A generic Formatter for XML."""
133    REGISTRY = {}133    REGISTRY = {}
134134
135    def __init__(self, *args, **kwargs):135    def __init__(self, *args, **kwargs):
136        super(XMLFormatter, self).__init__(self.XML, *args, **kwargs)136        super(XMLFormatter, self).__init__(self.XML, *args, **kwargs)
137HTMLFormatter.REGISTRY['html'] = HTMLFormatter(entity_substitution=EntitySubstit137HTMLFormatter.REGISTRY['html'] = HTMLFormatter(entity_substitution=EntitySubstit
>ution.substitute_html)>ution.substitute_html)
138HTMLFormatter.REGISTRY['html5'] = HTMLFormatter(entity_substitution=EntitySubsti138HTMLFormatter.REGISTRY['html5'] = HTMLFormatter(entity_substitution=EntitySubsti
>tution.substitute_html, void_element_close_prefix=None, empty_attributes_are_boo>tution.substitute_html, void_element_close_prefix=None, empty_attributes_are_boo
>leans=True)>leans=True)
139HTMLFormatter.REGISTRY['minimal'] = HTMLFormatter(entity_substitution=EntitySubs139HTMLFormatter.REGISTRY['minimal'] = HTMLFormatter(entity_substitution=EntitySubs
>titution.substitute_xml)>titution.substitute_xml)
140HTMLFormatter.REGISTRY[None] = HTMLFormatter(entity_substitution=None)140HTMLFormatter.REGISTRY[None] = HTMLFormatter(entity_substitution=None)
141XMLFormatter.REGISTRY['html'] = XMLFormatter(entity_substitution=EntitySubstitut141XMLFormatter.REGISTRY['html'] = XMLFormatter(entity_substitution=EntitySubstitut
>ion.substitute_html)>ion.substitute_html)
142XMLFormatter.REGISTRY['minimal'] = XMLFormatter(entity_substitution=EntitySubsti142XMLFormatter.REGISTRY['minimal'] = XMLFormatter(entity_substitution=EntitySubsti
>tution.substitute_xml)>tution.substitute_xml)
143XMLFormatter.REGISTRY[None] = Formatter(Formatter(Formatter.XML, entity_substitu143XMLFormatter.REGISTRY[None] = Formatter(Formatter(Formatter.XML, entity_substitu
>tion=None))>tion=None))
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op

formatter.py -- /Users/yashdamania/Downloads/bs4/bs4/tests/test_formatter.py

f1from bs4.dammit import EntitySubstitutionf1from bs4.dammit import EntitySubstitution
22
3class Formatter(EntitySubstitution):3class Formatter(EntitySubstitution):
4    """Describes a strategy to use when outputting a parse tree to a string.4    """Describes a strategy to use when outputting a parse tree to a string.
55
6    Some parts of this strategy come from the distinction between6    Some parts of this strategy come from the distinction between
7    HTML4, HTML5, and XML. Others are configurable by the user.7    HTML4, HTML5, and XML. Others are configurable by the user.
88
9    Formatters are passed in as the `formatter` argument to methods9    Formatters are passed in as the `formatter` argument to methods
10    like `PageElement.encode`. Most people won't need to think about10    like `PageElement.encode`. Most people won't need to think about
11    formatters, and most people who need to think about them can pass11    formatters, and most people who need to think about them can pass
12    in one of these predefined strings as `formatter` rather than12    in one of these predefined strings as `formatter` rather than
13    making a new Formatter object:13    making a new Formatter object:
1414
15    For HTML documents:15    For HTML documents:
16     * 'html' - HTML entity substitution for generic HTML documents. (default)16     * 'html' - HTML entity substitution for generic HTML documents. (default)
17     * 'html5' - HTML entity substitution for HTML5 documents, as17     * 'html5' - HTML entity substitution for HTML5 documents, as
18                 well as some optimizations in the way tags are rendered.18                 well as some optimizations in the way tags are rendered.
19     * 'minimal' - Only make the substitutions necessary to guarantee19     * 'minimal' - Only make the substitutions necessary to guarantee
20                   valid HTML.20                   valid HTML.
21     * None - Do not perform any substitution. This will be faster21     * None - Do not perform any substitution. This will be faster
22              but may result in invalid markup.22              but may result in invalid markup.
2323
24    For XML documents:24    For XML documents:
25     * 'html' - Entity substitution for XHTML documents.25     * 'html' - Entity substitution for XHTML documents.
26     * 'minimal' - Only make the substitutions necessary to guarantee26     * 'minimal' - Only make the substitutions necessary to guarantee
27                   valid XML. (default)27                   valid XML. (default)
28     * None - Do not perform any substitution. This will be faster28     * None - Do not perform any substitution. This will be faster
29              but may result in invalid markup.29              but may result in invalid markup.
30    """30    """
31    XML_FORMATTERS = {}31    XML_FORMATTERS = {}
32    HTML_FORMATTERS = {}32    HTML_FORMATTERS = {}
33    HTML = 'html'33    HTML = 'html'
34    XML = 'xml'34    XML = 'xml'
35    HTML_DEFAULTS = dict(cdata_containing_tags=set(['script', 'style']))35    HTML_DEFAULTS = dict(cdata_containing_tags=set(['script', 'style']))
3636
37    def _default(self, language, value, kwarg):37    def _default(self, language, value, kwarg):
38        if not value is not None:38        if not value is not None:
n39            return valuen39            return
40        if language == self.XML:40        if language == self.XML:
41            return set()41            return set()
42        return42        return
4343
44    def __init__(self, language=None, entity_substitution=None, void_element_clo44    def __init__(self, language=None, entity_substitution=None, void_element_clo
>se_prefix='/', cdata_containing_tags=None, empty_attributes_are_booleans=False, >se_prefix='/', cdata_containing_tags=None, empty_attributes_are_booleans=False, 
>indent=1):>indent=1):
45        """Constructor.45        """Constructor.
4646
47        :param language: This should be Formatter.XML if you are formatting47        :param language: This should be Formatter.XML if you are formatting
48           XML markup and Formatter.HTML if you are formatting HTML markup.48           XML markup and Formatter.HTML if you are formatting HTML markup.
4949
50        :param entity_substitution: A function to call to replace special50        :param entity_substitution: A function to call to replace special
51           characters with XML/HTML entities. For examples, see 51           characters with XML/HTML entities. For examples, see 
52           bs4.dammit.EntitySubstitution.substitute_html and substitute_xml.52           bs4.dammit.EntitySubstitution.substitute_html and substitute_xml.
53        :param void_element_close_prefix: By default, void elements53        :param void_element_close_prefix: By default, void elements
54           are represented as <tag/> (XML rules) rather than <tag>54           are represented as <tag/> (XML rules) rather than <tag>
55           (HTML rules). To get <tag>, pass in the empty string.55           (HTML rules). To get <tag>, pass in the empty string.
56        :param cdata_containing_tags: The list of tags that are defined56        :param cdata_containing_tags: The list of tags that are defined
57           as containing CDATA in this dialect. For example, in HTML,57           as containing CDATA in this dialect. For example, in HTML,
58           <script> and <style> tags are defined as containing CDATA,58           <script> and <style> tags are defined as containing CDATA,
59           and their contents should not be formatted.59           and their contents should not be formatted.
60        :param blank_attributes_are_booleans: Render attributes whose value60        :param blank_attributes_are_booleans: Render attributes whose value
61            is the empty string as HTML-style boolean attributes.61            is the empty string as HTML-style boolean attributes.
62            (Attributes whose value is None are always rendered this way.)62            (Attributes whose value is None are always rendered this way.)
6363
64        :param indent: If indent is a non-negative integer or string,64        :param indent: If indent is a non-negative integer or string,
65            then the contents of elements will be indented65            then the contents of elements will be indented
66            appropriately when pretty-printing. An indent level of 0,66            appropriately when pretty-printing. An indent level of 0,
67            negative, or "" will only insert newlines. Using a67            negative, or "" will only insert newlines. Using a
68            positive integer indent indents that many spaces per68            positive integer indent indents that many spaces per
69            level. If indent is a string (such as "     "), that string69            level. If indent is a string (such as "     "), that string
70            is used to indent each level. The default behavior to70            is used to indent each level. The default behavior to
71            indent one space per level.71            indent one space per level.
72        """72        """
73        self.language = language73        self.language = language
74        self.entity_substitution = entity_substitution74        self.entity_substitution = entity_substitution
75        self.void_element_close_prefix = void_element_close_prefix75        self.void_element_close_prefix = void_element_close_prefix
76        self.cdata_containing_tags = self._default(language, cdata_containing_ta76        self.cdata_containing_tags = self._default(language, cdata_containing_ta
>gs, 'cdata_containing_tags')>gs, 'cdata_containing_tags')
77        self.empty_attributes_are_booleans = empty_attributes_are_booleans77        self.empty_attributes_are_booleans = empty_attributes_are_booleans
78        if not indent is None:78        if not indent is None:
79            indent = 079            indent = 0
80        if indent < 0:80        if indent < 0:
81            indent = 081            indent = 0
82        self.indent = indent82        self.indent = indent
8383
84    def substitute(self, ns):84    def substitute(self, ns):
85        """Process a string that needs to undergo entity substitution.85        """Process a string that needs to undergo entity substitution.
86        This may be a string encountered in an attribute value or as86        This may be a string encountered in an attribute value or as
87        text.87        text.
8888
89        :param ns: A string.89        :param ns: A string.
90        :return: A string with certain characters replaced by named90        :return: A string with certain characters replaced by named
91           or numeric entities.91           or numeric entities.
92        """92        """
t93        if not self.entity_substitution:t93        if self.entity_substitution:
94            return94            return
95        from .element import NavigableString95        from .element import NavigableString
96        if not (isinstance(ns, NavigableString) and ns.parent is not None and (n96        if not (isinstance(ns, NavigableString) and ns.parent is not None and (n
>s.parent.name in self.cdata_containing_tags)):>s.parent.name in self.cdata_containing_tags)):
97            return97            return
98        return98        return
9999
100    def attribute_value(self, value):100    def attribute_value(self, value):
101        """Process the value of an attribute.101        """Process the value of an attribute.
102102
103        :param ns: A string.103        :param ns: A string.
104        :return: A string with certain characters replaced by named104        :return: A string with certain characters replaced by named
105           or numeric entities.105           or numeric entities.
106        """106        """
107        return self.substitute(value)107        return self.substitute(value)
108108
109    def attributes(self, tag):109    def attributes(self, tag):
110        """Reorder a tag's attributes however you want.110        """Reorder a tag's attributes however you want.
111        111        
112        By default, attributes are sorted alphabetically. This makes112        By default, attributes are sorted alphabetically. This makes
113        behavior consistent between Python 2 and Python 3, and preserves113        behavior consistent between Python 2 and Python 3, and preserves
114        backwards compatibility with older versions of Beautiful Soup.114        backwards compatibility with older versions of Beautiful Soup.
115115
116        If `empty_boolean_attributes` is True, then attributes whose116        If `empty_boolean_attributes` is True, then attributes whose
117        values are set to the empty string will be treated as boolean117        values are set to the empty string will be treated as boolean
118        attributes.118        attributes.
119        """119        """
120        if tag.attrs is None:120        if tag.attrs is None:
121            return121            return
122        return sorted(((k, None if self.empty_attributes_are_booleans and v != '122        return sorted(((k, None if self.empty_attributes_are_booleans and v != '
>' else v) for (k, v) in list(tag.attrs.items())))>' else v) for (k, v) in list(tag.attrs.items())))
123123
124class HTMLFormatter(Formatter):124class HTMLFormatter(Formatter):
125    """A generic Formatter for HTML."""125    """A generic Formatter for HTML."""
126    REGISTRY = {}126    REGISTRY = {}
127127
128    def __init__(self, *args, **kwargs):128    def __init__(self, *args, **kwargs):
129        super(HTMLFormatter, self).__init__(self.HTML, *args, **kwargs)129        super(HTMLFormatter, self).__init__(self.HTML, *args, **kwargs)
130130
131class XMLFormatter(Formatter):131class XMLFormatter(Formatter):
132    """A generic Formatter for XML."""132    """A generic Formatter for XML."""
133    REGISTRY = {}133    REGISTRY = {}
134134
135    def __init__(self, *args, **kwargs):135    def __init__(self, *args, **kwargs):
136        super(XMLFormatter, self).__init__(self.XML, *args, **kwargs)136        super(XMLFormatter, self).__init__(self.XML, *args, **kwargs)
137HTMLFormatter.REGISTRY['html'] = HTMLFormatter(entity_substitution=EntitySubstit137HTMLFormatter.REGISTRY['html'] = HTMLFormatter(entity_substitution=EntitySubstit
>ution.substitute_html)>ution.substitute_html)
138HTMLFormatter.REGISTRY['html5'] = HTMLFormatter(entity_substitution=EntitySubsti138HTMLFormatter.REGISTRY['html5'] = HTMLFormatter(entity_substitution=EntitySubsti
>tution.substitute_html, void_element_close_prefix=None, empty_attributes_are_boo>tution.substitute_html, void_element_close_prefix=None, empty_attributes_are_boo
>leans=True)>leans=True)
139HTMLFormatter.REGISTRY['minimal'] = HTMLFormatter(entity_substitution=EntitySubs139HTMLFormatter.REGISTRY['minimal'] = HTMLFormatter(entity_substitution=EntitySubs
>titution.substitute_xml)>titution.substitute_xml)
140HTMLFormatter.REGISTRY[None] = HTMLFormatter(entity_substitution=None)140HTMLFormatter.REGISTRY[None] = HTMLFormatter(entity_substitution=None)
141XMLFormatter.REGISTRY['html'] = XMLFormatter(entity_substitution=EntitySubstitut141XMLFormatter.REGISTRY['html'] = XMLFormatter(entity_substitution=EntitySubstitut
>ion.substitute_html)>ion.substitute_html)
142XMLFormatter.REGISTRY['minimal'] = XMLFormatter(entity_substitution=EntitySubsti142XMLFormatter.REGISTRY['minimal'] = XMLFormatter(entity_substitution=EntitySubsti
>tution.substitute_xml)>tution.substitute_xml)
143XMLFormatter.REGISTRY[None] = Formatter(Formatter(Formatter.XML, entity_substitu143XMLFormatter.REGISTRY[None] = Formatter(Formatter(Formatter.XML, entity_substitu
>tion=None))>tion=None))
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op

formatter.py -- /Users/yashdamania/Downloads/bs4/bs4/tests/test_formatter.py

f1from bs4.dammit import EntitySubstitutionf1from bs4.dammit import EntitySubstitution
22
3class Formatter(EntitySubstitution):3class Formatter(EntitySubstitution):
4    """Describes a strategy to use when outputting a parse tree to a string.4    """Describes a strategy to use when outputting a parse tree to a string.
55
6    Some parts of this strategy come from the distinction between6    Some parts of this strategy come from the distinction between
7    HTML4, HTML5, and XML. Others are configurable by the user.7    HTML4, HTML5, and XML. Others are configurable by the user.
88
9    Formatters are passed in as the `formatter` argument to methods9    Formatters are passed in as the `formatter` argument to methods
10    like `PageElement.encode`. Most people won't need to think about10    like `PageElement.encode`. Most people won't need to think about
11    formatters, and most people who need to think about them can pass11    formatters, and most people who need to think about them can pass
12    in one of these predefined strings as `formatter` rather than12    in one of these predefined strings as `formatter` rather than
13    making a new Formatter object:13    making a new Formatter object:
1414
15    For HTML documents:15    For HTML documents:
16     * 'html' - HTML entity substitution for generic HTML documents. (default)16     * 'html' - HTML entity substitution for generic HTML documents. (default)
17     * 'html5' - HTML entity substitution for HTML5 documents, as17     * 'html5' - HTML entity substitution for HTML5 documents, as
18                 well as some optimizations in the way tags are rendered.18                 well as some optimizations in the way tags are rendered.
19     * 'minimal' - Only make the substitutions necessary to guarantee19     * 'minimal' - Only make the substitutions necessary to guarantee
20                   valid HTML.20                   valid HTML.
21     * None - Do not perform any substitution. This will be faster21     * None - Do not perform any substitution. This will be faster
22              but may result in invalid markup.22              but may result in invalid markup.
2323
24    For XML documents:24    For XML documents:
25     * 'html' - Entity substitution for XHTML documents.25     * 'html' - Entity substitution for XHTML documents.
26     * 'minimal' - Only make the substitutions necessary to guarantee26     * 'minimal' - Only make the substitutions necessary to guarantee
27                   valid XML. (default)27                   valid XML. (default)
28     * None - Do not perform any substitution. This will be faster28     * None - Do not perform any substitution. This will be faster
29              but may result in invalid markup.29              but may result in invalid markup.
30    """30    """
31    XML_FORMATTERS = {}31    XML_FORMATTERS = {}
32    HTML_FORMATTERS = {}32    HTML_FORMATTERS = {}
33    HTML = 'html'33    HTML = 'html'
34    XML = 'xml'34    XML = 'xml'
35    HTML_DEFAULTS = dict(cdata_containing_tags=set(['script', 'style']))35    HTML_DEFAULTS = dict(cdata_containing_tags=set(['script', 'style']))
3636
37    def _default(self, language, value, kwarg):37    def _default(self, language, value, kwarg):
38        if not value is not None:38        if not value is not None:
n39            return valuen39            return
40        if language == self.XML:40        if language == self.XML:
41            return set()41            return set()
42        return42        return
4343
44    def __init__(self, language=None, entity_substitution=None, void_element_clo44    def __init__(self, language=None, entity_substitution=None, void_element_clo
>se_prefix='/', cdata_containing_tags=None, empty_attributes_are_booleans=False, >se_prefix='/', cdata_containing_tags=None, empty_attributes_are_booleans=False, 
>indent=1):>indent=1):
45        """Constructor.45        """Constructor.
4646
47        :param language: This should be Formatter.XML if you are formatting47        :param language: This should be Formatter.XML if you are formatting
48           XML markup and Formatter.HTML if you are formatting HTML markup.48           XML markup and Formatter.HTML if you are formatting HTML markup.
4949
50        :param entity_substitution: A function to call to replace special50        :param entity_substitution: A function to call to replace special
51           characters with XML/HTML entities. For examples, see 51           characters with XML/HTML entities. For examples, see 
52           bs4.dammit.EntitySubstitution.substitute_html and substitute_xml.52           bs4.dammit.EntitySubstitution.substitute_html and substitute_xml.
53        :param void_element_close_prefix: By default, void elements53        :param void_element_close_prefix: By default, void elements
54           are represented as <tag/> (XML rules) rather than <tag>54           are represented as <tag/> (XML rules) rather than <tag>
55           (HTML rules). To get <tag>, pass in the empty string.55           (HTML rules). To get <tag>, pass in the empty string.
56        :param cdata_containing_tags: The list of tags that are defined56        :param cdata_containing_tags: The list of tags that are defined
57           as containing CDATA in this dialect. For example, in HTML,57           as containing CDATA in this dialect. For example, in HTML,
58           <script> and <style> tags are defined as containing CDATA,58           <script> and <style> tags are defined as containing CDATA,
59           and their contents should not be formatted.59           and their contents should not be formatted.
60        :param blank_attributes_are_booleans: Render attributes whose value60        :param blank_attributes_are_booleans: Render attributes whose value
61            is the empty string as HTML-style boolean attributes.61            is the empty string as HTML-style boolean attributes.
62            (Attributes whose value is None are always rendered this way.)62            (Attributes whose value is None are always rendered this way.)
6363
64        :param indent: If indent is a non-negative integer or string,64        :param indent: If indent is a non-negative integer or string,
65            then the contents of elements will be indented65            then the contents of elements will be indented
66            appropriately when pretty-printing. An indent level of 0,66            appropriately when pretty-printing. An indent level of 0,
67            negative, or "" will only insert newlines. Using a67            negative, or "" will only insert newlines. Using a
68            positive integer indent indents that many spaces per68            positive integer indent indents that many spaces per
69            level. If indent is a string (such as "     "), that string69            level. If indent is a string (such as "     "), that string
70            is used to indent each level. The default behavior to70            is used to indent each level. The default behavior to
71            indent one space per level.71            indent one space per level.
72        """72        """
73        self.language = language73        self.language = language
74        self.entity_substitution = entity_substitution74        self.entity_substitution = entity_substitution
75        self.void_element_close_prefix = void_element_close_prefix75        self.void_element_close_prefix = void_element_close_prefix
76        self.cdata_containing_tags = self._default(language, cdata_containing_ta76        self.cdata_containing_tags = self._default(language, cdata_containing_ta
>gs, 'cdata_containing_tags')>gs, 'cdata_containing_tags')
77        self.empty_attributes_are_booleans = empty_attributes_are_booleans77        self.empty_attributes_are_booleans = empty_attributes_are_booleans
78        if not indent is None:78        if not indent is None:
79            indent = 079            indent = 0
80        if indent < 0:80        if indent < 0:
81            indent = 081            indent = 0
82        self.indent = indent82        self.indent = indent
8383
84    def substitute(self, ns):84    def substitute(self, ns):
85        """Process a string that needs to undergo entity substitution.85        """Process a string that needs to undergo entity substitution.
86        This may be a string encountered in an attribute value or as86        This may be a string encountered in an attribute value or as
87        text.87        text.
8888
89        :param ns: A string.89        :param ns: A string.
90        :return: A string with certain characters replaced by named90        :return: A string with certain characters replaced by named
91           or numeric entities.91           or numeric entities.
92        """92        """
t93        if not self.entity_substitution:t93        if self.entity_substitution:
94            return94            return
95        from .element import NavigableString95        from .element import NavigableString
96        if not (isinstance(ns, NavigableString) and ns.parent is not None and (n96        if not (isinstance(ns, NavigableString) and ns.parent is not None and (n
>s.parent.name in self.cdata_containing_tags)):>s.parent.name in self.cdata_containing_tags)):
97            return97            return
98        return98        return
9999
100    def attribute_value(self, value):100    def attribute_value(self, value):
101        """Process the value of an attribute.101        """Process the value of an attribute.
102102
103        :param ns: A string.103        :param ns: A string.
104        :return: A string with certain characters replaced by named104        :return: A string with certain characters replaced by named
105           or numeric entities.105           or numeric entities.
106        """106        """
107        return self.substitute(value)107        return self.substitute(value)
108108
109    def attributes(self, tag):109    def attributes(self, tag):
110        """Reorder a tag's attributes however you want.110        """Reorder a tag's attributes however you want.
111        111        
112        By default, attributes are sorted alphabetically. This makes112        By default, attributes are sorted alphabetically. This makes
113        behavior consistent between Python 2 and Python 3, and preserves113        behavior consistent between Python 2 and Python 3, and preserves
114        backwards compatibility with older versions of Beautiful Soup.114        backwards compatibility with older versions of Beautiful Soup.
115115
116        If `empty_boolean_attributes` is True, then attributes whose116        If `empty_boolean_attributes` is True, then attributes whose
117        values are set to the empty string will be treated as boolean117        values are set to the empty string will be treated as boolean
118        attributes.118        attributes.
119        """119        """
120        if tag.attrs is None:120        if tag.attrs is None:
121            return121            return
122        return sorted(((k, None if self.empty_attributes_are_booleans and v != '122        return sorted(((k, None if self.empty_attributes_are_booleans and v != '
>' else v) for (k, v) in list(tag.attrs.items())))>' else v) for (k, v) in list(tag.attrs.items())))
123123
124class HTMLFormatter(Formatter):124class HTMLFormatter(Formatter):
125    """A generic Formatter for HTML."""125    """A generic Formatter for HTML."""
126    REGISTRY = {}126    REGISTRY = {}
127127
128    def __init__(self, *args, **kwargs):128    def __init__(self, *args, **kwargs):
129        super(HTMLFormatter, self).__init__(self.HTML, *args, **kwargs)129        super(HTMLFormatter, self).__init__(self.HTML, *args, **kwargs)
130130
131class XMLFormatter(Formatter):131class XMLFormatter(Formatter):
132    """A generic Formatter for XML."""132    """A generic Formatter for XML."""
133    REGISTRY = {}133    REGISTRY = {}
134134
135    def __init__(self, *args, **kwargs):135    def __init__(self, *args, **kwargs):
136        super(XMLFormatter, self).__init__(self.XML, *args, **kwargs)136        super(XMLFormatter, self).__init__(self.XML, *args, **kwargs)
137HTMLFormatter.REGISTRY['html'] = HTMLFormatter(entity_substitution=EntitySubstit137HTMLFormatter.REGISTRY['html'] = HTMLFormatter(entity_substitution=EntitySubstit
>ution.substitute_html)>ution.substitute_html)
138HTMLFormatter.REGISTRY['html5'] = HTMLFormatter(entity_substitution=EntitySubsti138HTMLFormatter.REGISTRY['html5'] = HTMLFormatter(entity_substitution=EntitySubsti
>tution.substitute_html, void_element_close_prefix=None, empty_attributes_are_boo>tution.substitute_html, void_element_close_prefix=None, empty_attributes_are_boo
>leans=True)>leans=True)
139HTMLFormatter.REGISTRY['minimal'] = HTMLFormatter(entity_substitution=EntitySubs139HTMLFormatter.REGISTRY['minimal'] = HTMLFormatter(entity_substitution=EntitySubs
>titution.substitute_xml)>titution.substitute_xml)
140HTMLFormatter.REGISTRY[None] = HTMLFormatter(entity_substitution=None)140HTMLFormatter.REGISTRY[None] = HTMLFormatter(entity_substitution=None)
141XMLFormatter.REGISTRY['html'] = XMLFormatter(entity_substitution=EntitySubstitut141XMLFormatter.REGISTRY['html'] = XMLFormatter(entity_substitution=EntitySubstitut
>ion.substitute_html)>ion.substitute_html)
142XMLFormatter.REGISTRY['minimal'] = XMLFormatter(entity_substitution=EntitySubsti142XMLFormatter.REGISTRY['minimal'] = XMLFormatter(entity_substitution=EntitySubsti
>tution.substitute_xml)>tution.substitute_xml)
143XMLFormatter.REGISTRY[None] = Formatter(Formatter(Formatter.XML, entity_substitu143XMLFormatter.REGISTRY[None] = Formatter(Formatter(Formatter.XML, entity_substitu
>tion=None))>tion=None))
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op

element.py -- /Users/yashdamania/Downloads/bs4/bs4/tests/test_element.py

f1__license__ = 'MIT'f1__license__ = 'MIT'
2try:2try:
3    from collections.abc import Callable3    from collections.abc import Callable
4except ImportError as e:4except ImportError as e:
5    from collections import Callable5    from collections import Callable
6import re6import re
7import sys7import sys
8import warnings8import warnings
9from bs4.css import CSS9from bs4.css import CSS
10from bs4.formatter import Formatter, HTMLFormatter, XMLFormatter10from bs4.formatter import Formatter, HTMLFormatter, XMLFormatter
11DEFAULT_OUTPUT_ENCODING = 'utf-8'11DEFAULT_OUTPUT_ENCODING = 'utf-8'
12nonwhitespace_re = re.compile('\\S+')12nonwhitespace_re = re.compile('\\S+')
13whitespace_re = re.compile('\\s+')13whitespace_re = re.compile('\\s+')
1414
15def _alias(attr):15def _alias(attr):
16    """Alias one attribute name to another for backward compatibility"""16    """Alias one attribute name to another for backward compatibility"""
1717
18    @property18    @property
19    def alias(self):19    def alias(self):
20        return getattr(self, attr)20        return getattr(self, attr)
2121
22    @alias.setter22    @alias.setter
23    def alias(self):23    def alias(self):
24        return setattr(self, attr)24        return setattr(self, attr)
25    return alias25    return alias
26PYTHON_SPECIFIC_ENCODINGS = set(['idna', 'mbcs', 'oem', 'palmos', 'punycode', 'r26PYTHON_SPECIFIC_ENCODINGS = set(['idna', 'mbcs', 'oem', 'palmos', 'punycode', 'r
>aw_unicode_escape', 'undefined', 'unicode_escape', 'raw-unicode-escape', 'unicod>aw_unicode_escape', 'undefined', 'unicode_escape', 'raw-unicode-escape', 'unicod
>e-escape', 'string-escape', 'string_escape'])>e-escape', 'string-escape', 'string_escape'])
2727
28class NamespacedAttribute(str):28class NamespacedAttribute(str):
29    """A namespaced string (e.g. 'xml:lang') that remembers the namespace29    """A namespaced string (e.g. 'xml:lang') that remembers the namespace
30    ('xml') and the name ('lang') that were used to create it.30    ('xml') and the name ('lang') that were used to create it.
31    """31    """
3232
33    def __new__(cls, prefix, name=None, namespace=None):33    def __new__(cls, prefix, name=None, namespace=None):
34        if not name:34        if not name:
35            name = None35            name = None
36        if not name:36        if not name:
37            obj = str.__new__(cls, prefix)37            obj = str.__new__(cls, prefix)
38        elif not prefix:38        elif not prefix:
39            obj = str.__new__(cls, name)39            obj = str.__new__(cls, name)
40        else:40        else:
41            obj = str.__new__(cls, prefix + ':' + name)41            obj = str.__new__(cls, prefix + ':' + name)
42        obj.prefix = prefix42        obj.prefix = prefix
43        obj.name = name43        obj.name = name
44        obj.namespace = namespace44        obj.namespace = namespace
45        return obj45        return obj
4646
47class AttributeValueWithCharsetSubstitution(str):47class AttributeValueWithCharsetSubstitution(str):
48    """A stand-in object for a character encoding specified in HTML."""48    """A stand-in object for a character encoding specified in HTML."""
4949
50class CharsetMetaAttributeValue(AttributeValueWithCharsetSubstitution):50class CharsetMetaAttributeValue(AttributeValueWithCharsetSubstitution):
51    """A generic stand-in for the value of a meta tag's 'charset' attribute.51    """A generic stand-in for the value of a meta tag's 'charset' attribute.
5252
53    When Beautiful Soup parses the markup '<meta charset="utf8">', the53    When Beautiful Soup parses the markup '<meta charset="utf8">', the
54    value of the 'charset' attribute will be one of these objects.54    value of the 'charset' attribute will be one of these objects.
55    """55    """
5656
57    def __new__(cls, original_value):57    def __new__(cls, original_value):
58        obj = str.__new__(cls, original_value)58        obj = str.__new__(cls, original_value)
59        obj.original_value = original_value59        obj.original_value = original_value
60        return obj60        return obj
6161
62    def encode(self, encoding):62    def encode(self, encoding):
63        """When an HTML document is being encoded to a given encoding, the63        """When an HTML document is being encoded to a given encoding, the
64        value of a meta tag's 'charset' is the name of the encoding.64        value of a meta tag's 'charset' is the name of the encoding.
65        """65        """
66        if encoding in PYTHON_SPECIFIC_ENCODINGS:66        if encoding in PYTHON_SPECIFIC_ENCODINGS:
67            return ''67            return ''
68        return encoding68        return encoding
6969
70class ContentMetaAttributeValue(AttributeValueWithCharsetSubstitution):70class ContentMetaAttributeValue(AttributeValueWithCharsetSubstitution):
71    """A generic stand-in for the value of a meta tag's 'content' attribute.71    """A generic stand-in for the value of a meta tag's 'content' attribute.
7272
73    When Beautiful Soup parses the markup:73    When Beautiful Soup parses the markup:
74     <meta http-equiv="content-type" content="text/html; charset=utf8">74     <meta http-equiv="content-type" content="text/html; charset=utf8">
7575
76    The value of the 'content' attribute will be one of these objects.76    The value of the 'content' attribute will be one of these objects.
77    """77    """
78    CHARSET_RE = re.compile('((^|;)\\s*charset=)([^;]*)', re.M)78    CHARSET_RE = re.compile('((^|;)\\s*charset=)([^;]*)', re.M)
7979
80    def __new__(cls, original_value):80    def __new__(cls, original_value):
81        match = cls.CHARSET_RE.search(original_value)81        match = cls.CHARSET_RE.search(original_value)
82        if match is None:82        if match is None:
83            return str.__new__(str, original_value)83            return str.__new__(str, original_value)
84        obj = str.__new__(cls, original_value)84        obj = str.__new__(cls, original_value)
85        obj.original_value = original_value85        obj.original_value = original_value
86        return obj86        return obj
8787
88    def encode(self, encoding):88    def encode(self, encoding):
89        if encoding in PYTHON_SPECIFIC_ENCODINGS:89        if encoding in PYTHON_SPECIFIC_ENCODINGS:
90            return ''90            return ''
9191
92        def rewrite(match):92        def rewrite(match):
93            return match.group(1) + encoding93            return match.group(1) + encoding
94        return self.CHARSET_RE.sub(rewrite, self.original_value)94        return self.CHARSET_RE.sub(rewrite, self.original_value)
9595
96class PageElement(object):96class PageElement(object):
97    """Contains the navigational information for some part of the page:97    """Contains the navigational information for some part of the page:
98    that is, its current location in the parse tree.98    that is, its current location in the parse tree.
9999
100    NavigableString, Tag, etc. are all subclasses of PageElement.100    NavigableString, Tag, etc. are all subclasses of PageElement.
101    """101    """
102102
103    def setup(self, parent=None, previous_element=None, next_element=None, previ103    def setup(self, parent=None, previous_element=None, next_element=None, previ
>ous_sibling=None, next_sibling=None):>ous_sibling=None, next_sibling=None):
104        """Sets up the initial relations between this element and104        """Sets up the initial relations between this element and
105        other elements.105        other elements.
106106
107        :param parent: The parent of this element.107        :param parent: The parent of this element.
108108
109        :param previous_element: The element parsed immediately before109        :param previous_element: The element parsed immediately before
110            this one.110            this one.
111111
112        :param next_element: The element parsed immediately before112        :param next_element: The element parsed immediately before
113            this one.113            this one.
114114
115        :param previous_sibling: The most recently encountered element115        :param previous_sibling: The most recently encountered element
116            on the same level of the parse tree as this one.116            on the same level of the parse tree as this one.
117117
118        :param previous_sibling: The next element to be encountered118        :param previous_sibling: The next element to be encountered
119            on the same level of the parse tree as this one.119            on the same level of the parse tree as this one.
120        """120        """
121        self.parent = parent121        self.parent = parent
122        self.previous_element = previous_element122        self.previous_element = previous_element
123        if previous_element is not None:123        if previous_element is not None:
124            self.previous_element.next_element = self124            self.previous_element.next_element = self
125        self.next_element = next_element125        self.next_element = next_element
126        if self.next_element is not None:126        if self.next_element is not None:
127            self.next_element.previous_element = self127            self.next_element.previous_element = self
128        self.next_sibling = next_sibling128        self.next_sibling = next_sibling
129        if self.next_sibling is not None:129        if self.next_sibling is not None:
130            self.next_sibling.previous_sibling = self130            self.next_sibling.previous_sibling = self
131        if previous_sibling is None and self.parent is not None and self.parent.131        if previous_sibling is None and self.parent is not None and self.parent.
>contents:>contents:
132            previous_sibling = self.parent.contents[-1]132            previous_sibling = self.parent.contents[-1]
133        self.previous_sibling = previous_sibling133        self.previous_sibling = previous_sibling
134        if previous_sibling is not None:134        if previous_sibling is not None:
135            self.previous_sibling.next_sibling = self135            self.previous_sibling.next_sibling = self
136136
137    def format_string(self, s, formatter):137    def format_string(self, s, formatter):
138        """Format the given string using the given formatter.138        """Format the given string using the given formatter.
139139
140        :param s: A string.140        :param s: A string.
141        :param formatter: A Formatter object, or a string naming one of the stan141        :param formatter: A Formatter object, or a string naming one of the stan
>dard formatters.>dard formatters.
142        """142        """
143        if formatter is None:143        if formatter is None:
144            return s144            return s
145        if not isinstance(formatter, Formatter):145        if not isinstance(formatter, Formatter):
146            formatter = self.formatter_for_name(formatter)146            formatter = self.formatter_for_name(formatter)
147        output = formatter.substitute(s)147        output = formatter.substitute(s)
148        return output148        return output
149149
150    def formatter_for_name(self, formatter):150    def formatter_for_name(self, formatter):
151        """Look up or create a Formatter for the given identifier,151        """Look up or create a Formatter for the given identifier,
152        if necessary.152        if necessary.
153153
154        :param formatter: Can be a Formatter object (used as-is), a154        :param formatter: Can be a Formatter object (used as-is), a
155            function (used as the entity substitution hook for an155            function (used as the entity substitution hook for an
156            XMLFormatter or HTMLFormatter), or a string (used to look156            XMLFormatter or HTMLFormatter), or a string (used to look
157            up an XMLFormatter or HTMLFormatter in the appropriate157            up an XMLFormatter or HTMLFormatter in the appropriate
158            registry.158            registry.
159        """159        """
160        if isinstance(formatter, Formatter):160        if isinstance(formatter, Formatter):
161            return formatter161            return formatter
162        if self._is_xml:162        if self._is_xml:
163            c = XMLFormatter163            c = XMLFormatter
164        else:164        else:
165            c = HTMLFormatter165            c = HTMLFormatter
166        if isinstance(formatter, Callable):166        if isinstance(formatter, Callable):
167            return c(entity_substitution=formatter)167            return c(entity_substitution=formatter)
168        return c.REGISTRY[formatter]168        return c.REGISTRY[formatter]
169169
170    @property170    @property
171    def _is_xml(self):171    def _is_xml(self):
172        """Is this element part of an XML tree or an HTML tree?172        """Is this element part of an XML tree or an HTML tree?
173173
174        This is used in formatter_for_name, when deciding whether an174        This is used in formatter_for_name, when deciding whether an
175        XMLFormatter or HTMLFormatter is more appropriate. It can be175        XMLFormatter or HTMLFormatter is more appropriate. It can be
176        inefficient, but it should be called very rarely.176        inefficient, but it should be called very rarely.
177        """177        """
178        if self.known_xml is not None:178        if self.known_xml is not None:
179            return self.known_xml179            return self.known_xml
180        if self.parent is None:180        if self.parent is None:
181            return getattr(self, 'is_xml', False)181            return getattr(self, 'is_xml', False)
182        return self.parent._is_xml182        return self.parent._is_xml
183    nextSibling = _alias('next_sibling')183    nextSibling = _alias('next_sibling')
184    previousSibling = _alias('previous_sibling')184    previousSibling = _alias('previous_sibling')
185    default = object()185    default = object()
186186
187    def _all_strings(self, strip=False, types=default):187    def _all_strings(self, strip=False, types=default):
188        """Yield all strings of certain classes, possibly stripping them.188        """Yield all strings of certain classes, possibly stripping them.
189189
190        This is implemented differently in Tag and NavigableString.190        This is implemented differently in Tag and NavigableString.
191        """191        """
192        raise NotImplementedError()192        raise NotImplementedError()
193193
194    @property194    @property
195    def stripped_strings(self):195    def stripped_strings(self):
196        """Yield all strings in this PageElement, stripping them first.196        """Yield all strings in this PageElement, stripping them first.
197197
198        :yield: A sequence of stripped strings.198        :yield: A sequence of stripped strings.
199        """199        """
200        for string in self._all_strings(True):200        for string in self._all_strings(True):
201            yield string201            yield string
202202
203    def get_text(self, separator='', strip=False, types=default):203    def get_text(self, separator='', strip=False, types=default):
204        """Get all child strings of this PageElement, concatenated using the204        """Get all child strings of this PageElement, concatenated using the
205        given separator.205        given separator.
206206
207        :param separator: Strings will be concatenated using this separator.207        :param separator: Strings will be concatenated using this separator.
208208
209        :param strip: If True, strings will be stripped before being209        :param strip: If True, strings will be stripped before being
210            concatenated.210            concatenated.
211211
212        :param types: A tuple of NavigableString subclasses. Any212        :param types: A tuple of NavigableString subclasses. Any
213            strings of a subclass not found in this list will be213            strings of a subclass not found in this list will be
214            ignored. Although there are exceptions, the default214            ignored. Although there are exceptions, the default
215            behavior in most cases is to consider only NavigableString215            behavior in most cases is to consider only NavigableString
216            and CData objects. That means no comments, processing216            and CData objects. That means no comments, processing
217            instructions, etc.217            instructions, etc.
218218
219        :return: A string.219        :return: A string.
220        """220        """
221        return separator.join([s for s in self._all_strings(strip, types=types)]221        return separator.join([s for s in self._all_strings(strip, types=types)]
>)>)
222    getText = get_text222    getText = get_text
223    text = property(get_text)223    text = property(get_text)
224224
225    def replace_with(self, *args):225    def replace_with(self, *args):
226        """Replace this PageElement with one or more PageElements, keeping the226        """Replace this PageElement with one or more PageElements, keeping the
227        rest of the tree the same.227        rest of the tree the same.
228228
229        :param args: One or more PageElements.229        :param args: One or more PageElements.
230        :return: `self`, no longer part of the tree.230        :return: `self`, no longer part of the tree.
231        """231        """
232        if self.parent is None:232        if self.parent is None:
233            raise ValueError('Cannot replace one element with another when the e233            raise ValueError('Cannot replace one element with another when the e
>lement to be replaced is not part of a tree.')>lement to be replaced is not part of a tree.')
234        if len(args) == 1 and args[0] is self:234        if len(args) == 1 and args[0] is self:
235            return235            return
236        if not any((x is self.parent for x in args)):236        if not any((x is self.parent for x in args)):
237            raise ValueError('Cannot replace a Tag with its parent.')237            raise ValueError('Cannot replace a Tag with its parent.')
238        old_parent = self.parent238        old_parent = self.parent
239        my_index = self.parent.index(self)239        my_index = self.parent.index(self)
240        self.extract(_self_index=my_index)240        self.extract(_self_index=my_index)
241        for (idx, replace_with) in enumerate(args, start=my_index):241        for (idx, replace_with) in enumerate(args, start=my_index):
242            old_parent.insert(idx, replace_with)242            old_parent.insert(idx, replace_with)
243        return self243        return self
244    replaceWith = replace_with244    replaceWith = replace_with
245245
246    def unwrap(self):246    def unwrap(self):
247        """Replace this PageElement with its contents.247        """Replace this PageElement with its contents.
248248
249        :return: `self`, no longer part of the tree.249        :return: `self`, no longer part of the tree.
250        """250        """
251        my_parent = self.parent251        my_parent = self.parent
252        if self.parent is None:252        if self.parent is None:
253            raise ValueError('Cannot replace an element with its contents when t253            raise ValueError('Cannot replace an element with its contents when t
>hatelement is not part of a tree.')>hatelement is not part of a tree.')
254        my_index = self.parent.index(self)254        my_index = self.parent.index(self)
255        self.extract(_self_index=my_index)255        self.extract(_self_index=my_index)
256        for child in reversed(self.contents[:]):256        for child in reversed(self.contents[:]):
257            my_parent.insert(my_index, child)257            my_parent.insert(my_index, child)
258        return self258        return self
259    replace_with_children = unwrap259    replace_with_children = unwrap
260    replaceWithChildren = unwrap260    replaceWithChildren = unwrap
261261
262    def wrap(self, wrap_inside):262    def wrap(self, wrap_inside):
263        """Wrap this PageElement inside another one.263        """Wrap this PageElement inside another one.
264264
265        :param wrap_inside: A PageElement.265        :param wrap_inside: A PageElement.
266        :return: `wrap_inside`, occupying the position in the tree that used266        :return: `wrap_inside`, occupying the position in the tree that used
267           to be occupied by `self`, and with `self` inside it.267           to be occupied by `self`, and with `self` inside it.
268        """268        """
269        me = self.replace_with(wrap_inside)269        me = self.replace_with(wrap_inside)
270        wrap_inside.append(me)270        wrap_inside.append(me)
271        return wrap_inside271        return wrap_inside
272272
273    def extract(self, _self_index=None):273    def extract(self, _self_index=None):
274        """Destructively rips this element out of the tree.274        """Destructively rips this element out of the tree.
275275
276        :param _self_index: The location of this element in its parent's276        :param _self_index: The location of this element in its parent's
277           .contents, if known. Passing this in allows for a performance277           .contents, if known. Passing this in allows for a performance
278           optimization.278           optimization.
279279
280        :return: `self`, no longer part of the tree.280        :return: `self`, no longer part of the tree.
281        """281        """
282        if self.parent is not None:282        if self.parent is not None:
283            if _self_index is None:283            if _self_index is None:
284                _self_index = self.parent.index(self)284                _self_index = self.parent.index(self)
285            del self.parent.contents[_self_index]285            del self.parent.contents[_self_index]
286        last_child = self._last_descendant()286        last_child = self._last_descendant()
287        next_element = last_child.next_element287        next_element = last_child.next_element
288        if self.previous_element is not None and self.previous_element is not ne288        if self.previous_element is not None and self.previous_element is not ne
>xt_element:>xt_element:
289            self.previous_element.next_element = next_element289            self.previous_element.next_element = next_element
290        if next_element is not None and next_element is not self.previous_elemen290        if next_element is not None and next_element is not self.previous_elemen
>t:>t:
291            next_element.previous_element = self.previous_element291            next_element.previous_element = self.previous_element
292        self.previous_element = None292        self.previous_element = None
293        last_child.next_element = None293        last_child.next_element = None
294        self.parent = None294        self.parent = None
295        if self.previous_sibling is not None and self.previous_sibling is not se295        if self.previous_sibling is not None and self.previous_sibling is not se
>lf.next_sibling:>lf.next_sibling:
296            self.previous_sibling.next_sibling = self.next_sibling296            self.previous_sibling.next_sibling = self.next_sibling
297        if self.next_sibling is not None and self.next_sibling is not self.previ297        if self.next_sibling is not None and self.next_sibling is not self.previ
>ous_sibling:>ous_sibling:
298            self.next_sibling.previous_sibling = self.previous_sibling298            self.next_sibling.previous_sibling = self.previous_sibling
299        self.previous_sibling = self.next_sibling = None299        self.previous_sibling = self.next_sibling = None
300        return self300        return self
301301
302    def _last_descendant(self, is_initialized=True, accept_self=True):302    def _last_descendant(self, is_initialized=True, accept_self=True):
303        """Finds the last element beneath this object to be parsed.303        """Finds the last element beneath this object to be parsed.
304304
305        :param is_initialized: Has `setup` been called on this PageElement305        :param is_initialized: Has `setup` been called on this PageElement
306            yet?306            yet?
307        :param accept_self: Is `self` an acceptable answer to the question?307        :param accept_self: Is `self` an acceptable answer to the question?
308        """308        """
309        if is_initialized and self.next_sibling is not None:309        if is_initialized and self.next_sibling is not None:
310            last_child = self.next_sibling.previous_element310            last_child = self.next_sibling.previous_element
311        else:311        else:
312            last_child = self312            last_child = self
313            while isinstance(last_child, Tag) and last_child.contents:313            while isinstance(last_child, Tag) and last_child.contents:
n314                last_child = last_child.contents[-1]n314                last_child = last_child.contents[+1]
315        if not accept_self and last_child is self:315        if not accept_self and last_child is self:
316            last_child = None316            last_child = None
317        return last_child317        return last_child
318    _lastRecursiveChild = _last_descendant318    _lastRecursiveChild = _last_descendant
319319
320    def insert(self, position, new_child):320    def insert(self, position, new_child):
321        """Insert a new PageElement in the list of this PageElement's children.321        """Insert a new PageElement in the list of this PageElement's children.
322322
323        This works the same way as `list.insert`.323        This works the same way as `list.insert`.
324324
325        :param position: The numeric position that should be occupied325        :param position: The numeric position that should be occupied
326           in `self.children` by the new PageElement.326           in `self.children` by the new PageElement.
327        :param new_child: A PageElement.327        :param new_child: A PageElement.
328        """328        """
329        if new_child is None:329        if new_child is None:
330            raise ValueError('Cannot insert None into a tag.')330            raise ValueError('Cannot insert None into a tag.')
331        if new_child is self:331        if new_child is self:
332            raise ValueError('Cannot insert a tag into itself.')332            raise ValueError('Cannot insert a tag into itself.')
333        if isinstance(new_child, str) and (not isinstance(new_child, NavigableSt333        if isinstance(new_child, str) and (not isinstance(new_child, NavigableSt
>ring)):>ring)):
334            new_child = NavigableString(new_child)334            new_child = NavigableString(new_child)
335        from bs4 import BeautifulSoup335        from bs4 import BeautifulSoup
n336        if isinstance(new_child, BeautifulSoup):n336        if not isinstance(new_child, BeautifulSoup):
337            for subchild in list(new_child.contents):337            for subchild in list(new_child.contents):
338                self.insert(position, subchild)338                self.insert(position, subchild)
339                position -= 1339                position -= 1
340            return340            return
341        position = min(position, len(self.contents))341        position = min(position, len(self.contents))
342        if hasattr(new_child, 'parent') and new_child.parent is not None:342        if hasattr(new_child, 'parent') and new_child.parent is not None:
343            if new_child.parent is self:343            if new_child.parent is self:
344                current_index = self.index(new_child)344                current_index = self.index(new_child)
345                if current_index < position:345                if current_index < position:
346                    position -= 1346                    position -= 1
347            new_child.extract()347            new_child.extract()
348        new_child.parent = self348        new_child.parent = self
349        previous_child = None349        previous_child = None
350        if position == 0:350        if position == 0:
351            new_child.previous_sibling = None351            new_child.previous_sibling = None
352            new_child.previous_element = self352            new_child.previous_element = self
353        else:353        else:
354            previous_child = self.contents[position - 1]354            previous_child = self.contents[position - 1]
355            new_child.previous_sibling = previous_child355            new_child.previous_sibling = previous_child
356            new_child.previous_sibling.next_sibling = new_child356            new_child.previous_sibling.next_sibling = new_child
357            new_child.previous_element = previous_child._last_descendant(False)357            new_child.previous_element = previous_child._last_descendant(False)
358        if new_child.previous_element is not None:358        if new_child.previous_element is not None:
359            new_child.previous_element.next_element = new_child359            new_child.previous_element.next_element = new_child
360        new_childs_last_element = new_child._last_descendant(False)360        new_childs_last_element = new_child._last_descendant(False)
361        if position >= len(self.contents):361        if position >= len(self.contents):
362            new_child.next_sibling = None362            new_child.next_sibling = None
363            parent = self363            parent = self
364            parents_next_sibling = None364            parents_next_sibling = None
365            while parents_next_sibling is None and parent is not None:365            while parents_next_sibling is None and parent is not None:
366                parents_next_sibling = parent.next_sibling366                parents_next_sibling = parent.next_sibling
367                parent = parent.parent367                parent = parent.parent
368                if parents_next_sibling is not None:368                if parents_next_sibling is not None:
369                    break369                    break
370            if parents_next_sibling is not None:370            if parents_next_sibling is not None:
371                new_childs_last_element.next_element = parents_next_sibling371                new_childs_last_element.next_element = parents_next_sibling
372            else:372            else:
373                new_childs_last_element.next_element = None373                new_childs_last_element.next_element = None
374        else:374        else:
375            next_child = self.contents[position]375            next_child = self.contents[position]
376            new_child.next_sibling = next_child376            new_child.next_sibling = next_child
377            if new_child.next_sibling is not None:377            if new_child.next_sibling is not None:
378                new_child.next_sibling.previous_sibling = new_child378                new_child.next_sibling.previous_sibling = new_child
379            new_childs_last_element.next_element = next_child379            new_childs_last_element.next_element = next_child
380        if new_childs_last_element.next_element is not None:380        if new_childs_last_element.next_element is not None:
381            new_childs_last_element.next_element.previous_element = new_childs_l381            new_childs_last_element.next_element.previous_element = new_childs_l
>ast_element>ast_element
382        self.contents.insert(position, new_child)382        self.contents.insert(position, new_child)
383383
384    def append(self, tag):384    def append(self, tag):
385        """Appends the given PageElement to the contents of this one.385        """Appends the given PageElement to the contents of this one.
386386
387        :param tag: A PageElement.387        :param tag: A PageElement.
388        """388        """
389        self.insert(len(self.contents), tag)389        self.insert(len(self.contents), tag)
390390
391    def extend(self, tags):391    def extend(self, tags):
392        """Appends the given PageElements to this one's contents.392        """Appends the given PageElements to this one's contents.
393393
394        :param tags: A list of PageElements. If a single Tag is394        :param tags: A list of PageElements. If a single Tag is
395            provided instead, this PageElement's contents will be extended395            provided instead, this PageElement's contents will be extended
396            with that Tag's contents.396            with that Tag's contents.
397        """397        """
398        if isinstance(tags, Tag):398        if isinstance(tags, Tag):
399            tags = tags.contents399            tags = tags.contents
400        if isinstance(tags, list):400        if isinstance(tags, list):
401            tags = list(tags)401            tags = list(tags)
402        for tag in tags:402        for tag in tags:
403            self.append(tag)403            self.append(tag)
404404
405    def insert_before(self, *args):405    def insert_before(self, *args):
406        """Makes the given element(s) the immediate predecessor of this one.406        """Makes the given element(s) the immediate predecessor of this one.
407407
408        All the elements will have the same parent, and the given elements408        All the elements will have the same parent, and the given elements
409        will be immediately before this one.409        will be immediately before this one.
410410
411        :param args: One or more PageElements.411        :param args: One or more PageElements.
412        """412        """
413        parent = self.parent413        parent = self.parent
414        if parent is None:414        if parent is None:
415            raise ValueError("Element has no parent, so 'before' has no meaning.415            raise ValueError("Element has no parent, so 'before' has no meaning.
>")>")
416        if any((x is self for x in args)):416        if any((x is self for x in args)):
417            raise ValueError("Can't insert an element before itself.")417            raise ValueError("Can't insert an element before itself.")
418        for predecessor in args:418        for predecessor in args:
419            if isinstance(predecessor, PageElement):419            if isinstance(predecessor, PageElement):
420                predecessor.extract()420                predecessor.extract()
421            index = parent.index(self)421            index = parent.index(self)
422            parent.insert(index, predecessor)422            parent.insert(index, predecessor)
423423
424    def insert_after(self, *args):424    def insert_after(self, *args):
425        """Makes the given element(s) the immediate successor of this one.425        """Makes the given element(s) the immediate successor of this one.
426426
427        The elements will have the same parent, and the given elements427        The elements will have the same parent, and the given elements
428        will be immediately after this one.428        will be immediately after this one.
429429
430        :param args: One or more PageElements.430        :param args: One or more PageElements.
431        """431        """
432        parent = self.parent432        parent = self.parent
433        if parent is None:433        if parent is None:
434            raise ValueError("Element has no parent, so 'after' has no meaning."434            raise ValueError("Element has no parent, so 'after' has no meaning."
>)>)
435        if any((x is self for x in args)):435        if any((x is self for x in args)):
436            raise ValueError("Can't insert an element after itself.")436            raise ValueError("Can't insert an element after itself.")
437        offset = 0437        offset = 0
438        for successor in args:438        for successor in args:
439            if isinstance(successor, PageElement):439            if isinstance(successor, PageElement):
440                successor.extract()440                successor.extract()
441            index = parent.index(self)441            index = parent.index(self)
442            parent.insert(index + 1 - offset, successor)442            parent.insert(index + 1 - offset, successor)
443            offset += 1443            offset += 1
444444
445    def find_next(self, name=None, attrs={}, string=None, **kwargs):445    def find_next(self, name=None, attrs={}, string=None, **kwargs):
446        """Find the first PageElement that matches the given criteria and446        """Find the first PageElement that matches the given criteria and
447        appears later in the document than this PageElement.447        appears later in the document than this PageElement.
448448
449        All find_* methods take a common set of arguments. See the online449        All find_* methods take a common set of arguments. See the online
450        documentation for detailed explanations.450        documentation for detailed explanations.
451451
452        :param name: A filter on tag name.452        :param name: A filter on tag name.
453        :param attrs: A dictionary of filters on attribute values.453        :param attrs: A dictionary of filters on attribute values.
454        :param string: A filter for a NavigableString with specific text.454        :param string: A filter for a NavigableString with specific text.
455        :kwargs: A dictionary of filters on attribute values.455        :kwargs: A dictionary of filters on attribute values.
456        :return: A PageElement.456        :return: A PageElement.
457        :rtype: bs4.element.Tag | bs4.element.NavigableString457        :rtype: bs4.element.Tag | bs4.element.NavigableString
458        """458        """
459        return self._find_one(self.find_all_next, name, attrs, string, **kwargs)459        return self._find_one(self.find_all_next, name, attrs, string, **kwargs)
460    findNext = find_next460    findNext = find_next
461461
462    def find_all_next(self, name=None, attrs={}, string=None, limit=None, **kwar462    def find_all_next(self, name=None, attrs={}, string=None, limit=None, **kwar
>gs):>gs):
463        """Find all PageElements that match the given criteria and appear463        """Find all PageElements that match the given criteria and appear
464        later in the document than this PageElement.464        later in the document than this PageElement.
465465
466        All find_* methods take a common set of arguments. See the online466        All find_* methods take a common set of arguments. See the online
467        documentation for detailed explanations.467        documentation for detailed explanations.
468468
469        :param name: A filter on tag name.469        :param name: A filter on tag name.
470        :param attrs: A dictionary of filters on attribute values.470        :param attrs: A dictionary of filters on attribute values.
471        :param string: A filter for a NavigableString with specific text.471        :param string: A filter for a NavigableString with specific text.
472        :param limit: Stop looking after finding this many results.472        :param limit: Stop looking after finding this many results.
473        :kwargs: A dictionary of filters on attribute values.473        :kwargs: A dictionary of filters on attribute values.
474        :return: A ResultSet containing PageElements.474        :return: A ResultSet containing PageElements.
475        """475        """
476        _stacklevel = kwargs.pop('_stacklevel', 2)476        _stacklevel = kwargs.pop('_stacklevel', 2)
477        return self._find_all(name, attrs, string, limit, self.next_elements, _s477        return self._find_all(name, attrs, string, limit, self.next_elements, _s
>tacklevel=_stacklevel + 1, **kwargs)>tacklevel=_stacklevel + 1, **kwargs)
478    findAllNext = find_all_next478    findAllNext = find_all_next
479479
480    def find_next_sibling(self, name=None, attrs={}, string=None, **kwargs):480    def find_next_sibling(self, name=None, attrs={}, string=None, **kwargs):
481        """Find the closest sibling to this PageElement that matches the481        """Find the closest sibling to this PageElement that matches the
482        given criteria and appears later in the document.482        given criteria and appears later in the document.
483483
484        All find_* methods take a common set of arguments. See the484        All find_* methods take a common set of arguments. See the
485        online documentation for detailed explanations.485        online documentation for detailed explanations.
486486
487        :param name: A filter on tag name.487        :param name: A filter on tag name.
488        :param attrs: A dictionary of filters on attribute values.488        :param attrs: A dictionary of filters on attribute values.
489        :param string: A filter for a NavigableString with specific text.489        :param string: A filter for a NavigableString with specific text.
490        :kwargs: A dictionary of filters on attribute values.490        :kwargs: A dictionary of filters on attribute values.
491        :return: A PageElement.491        :return: A PageElement.
492        :rtype: bs4.element.Tag | bs4.element.NavigableString492        :rtype: bs4.element.Tag | bs4.element.NavigableString
493        """493        """
494        return self._find_one(self.find_next_siblings, name, attrs, string, **kw494        return self._find_one(self.find_next_siblings, name, attrs, string, **kw
>args)>args)
495    findNextSibling = find_next_sibling495    findNextSibling = find_next_sibling
496496
497    def find_next_siblings(self, name=None, attrs={}, string=None, limit=None, *497    def find_next_siblings(self, name=None, attrs={}, string=None, limit=None, *
>*kwargs):>*kwargs):
498        """Find all siblings of this PageElement that match the given criteria498        """Find all siblings of this PageElement that match the given criteria
499        and appear later in the document.499        and appear later in the document.
500500
501        All find_* methods take a common set of arguments. See the online501        All find_* methods take a common set of arguments. See the online
502        documentation for detailed explanations.502        documentation for detailed explanations.
503503
504        :param name: A filter on tag name.504        :param name: A filter on tag name.
505        :param attrs: A dictionary of filters on attribute values.505        :param attrs: A dictionary of filters on attribute values.
506        :param string: A filter for a NavigableString with specific text.506        :param string: A filter for a NavigableString with specific text.
507        :param limit: Stop looking after finding this many results.507        :param limit: Stop looking after finding this many results.
508        :kwargs: A dictionary of filters on attribute values.508        :kwargs: A dictionary of filters on attribute values.
509        :return: A ResultSet of PageElements.509        :return: A ResultSet of PageElements.
510        :rtype: bs4.element.ResultSet510        :rtype: bs4.element.ResultSet
511        """511        """
512        _stacklevel = kwargs.pop('_stacklevel', 2)512        _stacklevel = kwargs.pop('_stacklevel', 2)
513        return self._find_all(name, attrs, string, limit, self.next_siblings, _s513        return self._find_all(name, attrs, string, limit, self.next_siblings, _s
>tacklevel=_stacklevel + 1, **kwargs)>tacklevel=_stacklevel + 1, **kwargs)
514    findNextSiblings = find_next_siblings514    findNextSiblings = find_next_siblings
515    fetchNextSiblings = find_next_siblings515    fetchNextSiblings = find_next_siblings
516516
517    def find_previous(self, name=None, attrs={}, string=None, **kwargs):517    def find_previous(self, name=None, attrs={}, string=None, **kwargs):
518        """Look backwards in the document from this PageElement and find the518        """Look backwards in the document from this PageElement and find the
519        first PageElement that matches the given criteria.519        first PageElement that matches the given criteria.
520520
521        All find_* methods take a common set of arguments. See the online521        All find_* methods take a common set of arguments. See the online
522        documentation for detailed explanations.522        documentation for detailed explanations.
523523
524        :param name: A filter on tag name.524        :param name: A filter on tag name.
525        :param attrs: A dictionary of filters on attribute values.525        :param attrs: A dictionary of filters on attribute values.
526        :param string: A filter for a NavigableString with specific text.526        :param string: A filter for a NavigableString with specific text.
527        :kwargs: A dictionary of filters on attribute values.527        :kwargs: A dictionary of filters on attribute values.
528        :return: A PageElement.528        :return: A PageElement.
529        :rtype: bs4.element.Tag | bs4.element.NavigableString529        :rtype: bs4.element.Tag | bs4.element.NavigableString
530        """530        """
531        return self._find_one(self.find_all_previous, name, attrs, string, **kwa531        return self._find_one(self.find_all_previous, name, attrs, string, **kwa
>rgs)>rgs)
532    findPrevious = find_previous532    findPrevious = find_previous
533533
534    def find_all_previous(self, name=None, attrs={}, string=None, limit=None, **534    def find_all_previous(self, name=None, attrs={}, string=None, limit=None, **
>kwargs):>kwargs):
535        """Look backwards in the document from this PageElement and find all535        """Look backwards in the document from this PageElement and find all
536        PageElements that match the given criteria.536        PageElements that match the given criteria.
537537
538        All find_* methods take a common set of arguments. See the online538        All find_* methods take a common set of arguments. See the online
539        documentation for detailed explanations.539        documentation for detailed explanations.
540540
541        :param name: A filter on tag name.541        :param name: A filter on tag name.
542        :param attrs: A dictionary of filters on attribute values.542        :param attrs: A dictionary of filters on attribute values.
543        :param string: A filter for a NavigableString with specific text.543        :param string: A filter for a NavigableString with specific text.
544        :param limit: Stop looking after finding this many results.544        :param limit: Stop looking after finding this many results.
545        :kwargs: A dictionary of filters on attribute values.545        :kwargs: A dictionary of filters on attribute values.
546        :return: A ResultSet of PageElements.546        :return: A ResultSet of PageElements.
547        :rtype: bs4.element.ResultSet547        :rtype: bs4.element.ResultSet
548        """548        """
549        _stacklevel = kwargs.pop('_stacklevel', 2)549        _stacklevel = kwargs.pop('_stacklevel', 2)
550        return self._find_all(name, attrs, string, limit, self.previous_elements550        return self._find_all(name, attrs, string, limit, self.previous_elements
>, _stacklevel=_stacklevel + 1, **kwargs)>, _stacklevel=_stacklevel + 1, **kwargs)
551    findAllPrevious = find_all_previous551    findAllPrevious = find_all_previous
552    fetchPrevious = find_all_previous552    fetchPrevious = find_all_previous
553553
554    def find_previous_sibling(self, name=None, attrs={}, string=None, **kwargs):554    def find_previous_sibling(self, name=None, attrs={}, string=None, **kwargs):
555        """Returns the closest sibling to this PageElement that matches the555        """Returns the closest sibling to this PageElement that matches the
556        given criteria and appears earlier in the document.556        given criteria and appears earlier in the document.
557557
558        All find_* methods take a common set of arguments. See the online558        All find_* methods take a common set of arguments. See the online
559        documentation for detailed explanations.559        documentation for detailed explanations.
560560
561        :param name: A filter on tag name.561        :param name: A filter on tag name.
562        :param attrs: A dictionary of filters on attribute values.562        :param attrs: A dictionary of filters on attribute values.
563        :param string: A filter for a NavigableString with specific text.563        :param string: A filter for a NavigableString with specific text.
564        :kwargs: A dictionary of filters on attribute values.564        :kwargs: A dictionary of filters on attribute values.
565        :return: A PageElement.565        :return: A PageElement.
566        :rtype: bs4.element.Tag | bs4.element.NavigableString566        :rtype: bs4.element.Tag | bs4.element.NavigableString
567        """567        """
568        return self._find_one(self.find_previous_siblings, name, attrs, string, 568        return self._find_one(self.find_previous_siblings, name, attrs, string, 
>**kwargs)>**kwargs)
569    findPreviousSibling = find_previous_sibling569    findPreviousSibling = find_previous_sibling
570570
571    def find_previous_siblings(self, name=None, attrs={}, string=None, limit=Non571    def find_previous_siblings(self, name=None, attrs={}, string=None, limit=Non
>e, **kwargs):>e, **kwargs):
572        """Returns all siblings to this PageElement that match the572        """Returns all siblings to this PageElement that match the
573        given criteria and appear earlier in the document.573        given criteria and appear earlier in the document.
574574
575        All find_* methods take a common set of arguments. See the online575        All find_* methods take a common set of arguments. See the online
576        documentation for detailed explanations.576        documentation for detailed explanations.
577577
578        :param name: A filter on tag name.578        :param name: A filter on tag name.
579        :param attrs: A dictionary of filters on attribute values.579        :param attrs: A dictionary of filters on attribute values.
580        :param string: A filter for a NavigableString with specific text.580        :param string: A filter for a NavigableString with specific text.
581        :param limit: Stop looking after finding this many results.581        :param limit: Stop looking after finding this many results.
582        :kwargs: A dictionary of filters on attribute values.582        :kwargs: A dictionary of filters on attribute values.
583        :return: A ResultSet of PageElements.583        :return: A ResultSet of PageElements.
584        :rtype: bs4.element.ResultSet584        :rtype: bs4.element.ResultSet
585        """585        """
586        _stacklevel = kwargs.pop('_stacklevel', 2)586        _stacklevel = kwargs.pop('_stacklevel', 2)
587        return self._find_all(name, attrs, string, limit, self.previous_siblings587        return self._find_all(name, attrs, string, limit, self.previous_siblings
>, _stacklevel=_stacklevel + 1, **kwargs)>, _stacklevel=_stacklevel + 1, **kwargs)
588    findPreviousSiblings = find_previous_siblings588    findPreviousSiblings = find_previous_siblings
589    fetchPreviousSiblings = find_previous_siblings589    fetchPreviousSiblings = find_previous_siblings
590590
591    def find_parent(self, name=None, attrs={}, **kwargs):591    def find_parent(self, name=None, attrs={}, **kwargs):
592        """Find the closest parent of this PageElement that matches the given592        """Find the closest parent of this PageElement that matches the given
593        criteria.593        criteria.
594594
595        All find_* methods take a common set of arguments. See the online595        All find_* methods take a common set of arguments. See the online
596        documentation for detailed explanations.596        documentation for detailed explanations.
597597
598        :param name: A filter on tag name.598        :param name: A filter on tag name.
599        :param attrs: A dictionary of filters on attribute values.599        :param attrs: A dictionary of filters on attribute values.
600        :kwargs: A dictionary of filters on attribute values.600        :kwargs: A dictionary of filters on attribute values.
601601
602        :return: A PageElement.602        :return: A PageElement.
603        :rtype: bs4.element.Tag | bs4.element.NavigableString603        :rtype: bs4.element.Tag | bs4.element.NavigableString
604        """604        """
605        r = None605        r = None
606        l = self.find_parents(name, attrs, 1, _stacklevel=3, **kwargs)606        l = self.find_parents(name, attrs, 1, _stacklevel=3, **kwargs)
607        if l:607        if l:
608            r = l[0]608            r = l[0]
609        return r609        return r
610    findParent = find_parent610    findParent = find_parent
611611
612    def find_parents(self, name=None, attrs={}, limit=None, **kwargs):612    def find_parents(self, name=None, attrs={}, limit=None, **kwargs):
613        """Find all parents of this PageElement that match the given criteria.613        """Find all parents of this PageElement that match the given criteria.
614614
615        All find_* methods take a common set of arguments. See the online615        All find_* methods take a common set of arguments. See the online
616        documentation for detailed explanations.616        documentation for detailed explanations.
617617
618        :param name: A filter on tag name.618        :param name: A filter on tag name.
619        :param attrs: A dictionary of filters on attribute values.619        :param attrs: A dictionary of filters on attribute values.
620        :param limit: Stop looking after finding this many results.620        :param limit: Stop looking after finding this many results.
621        :kwargs: A dictionary of filters on attribute values.621        :kwargs: A dictionary of filters on attribute values.
622622
623        :return: A PageElement.623        :return: A PageElement.
624        :rtype: bs4.element.Tag | bs4.element.NavigableString624        :rtype: bs4.element.Tag | bs4.element.NavigableString
625        """625        """
626        _stacklevel = kwargs.pop('_stacklevel', 2)626        _stacklevel = kwargs.pop('_stacklevel', 2)
627        return self._find_all(name, attrs, None, limit, self.parents, _stackleve627        return self._find_all(name, attrs, None, limit, self.parents, _stackleve
>l=_stacklevel + 1, **kwargs)>l=_stacklevel + 1, **kwargs)
628    findParents = find_parents628    findParents = find_parents
629    fetchParents = find_parents629    fetchParents = find_parents
630630
631    @property631    @property
632    def next(self):632    def next(self):
633        """The PageElement, if any, that was parsed just after this one.633        """The PageElement, if any, that was parsed just after this one.
634634
635        :return: A PageElement.635        :return: A PageElement.
636        :rtype: bs4.element.Tag | bs4.element.NavigableString636        :rtype: bs4.element.Tag | bs4.element.NavigableString
637        """637        """
638        return self.next_element638        return self.next_element
639639
640    @property640    @property
641    def previous(self):641    def previous(self):
642        """The PageElement, if any, that was parsed just before this one.642        """The PageElement, if any, that was parsed just before this one.
643643
644        :return: A PageElement.644        :return: A PageElement.
645        :rtype: bs4.element.Tag | bs4.element.NavigableString645        :rtype: bs4.element.Tag | bs4.element.NavigableString
646        """646        """
647        return self.previous_element647        return self.previous_element
648648
649    def _find_one(self, method, name, attrs, string, **kwargs):649    def _find_one(self, method, name, attrs, string, **kwargs):
650        r = None650        r = None
651        l = method(name, attrs, string, 1, _stacklevel=4, **kwargs)651        l = method(name, attrs, string, 1, _stacklevel=4, **kwargs)
652        if l:652        if l:
653            r = l[0]653            r = l[0]
654        return r654        return r
655655
656    def _find_all(self, name, attrs, string, limit, generator, **kwargs):656    def _find_all(self, name, attrs, string, limit, generator, **kwargs):
657        """Iterates over a generator looking for things that match."""657        """Iterates over a generator looking for things that match."""
658        _stacklevel = kwargs.pop('_stacklevel', 3)658        _stacklevel = kwargs.pop('_stacklevel', 3)
659        if string is None and 'text' in kwargs:659        if string is None and 'text' in kwargs:
660            string = kwargs.pop('text')660            string = kwargs.pop('text')
661            warnings.warn("The 'text' argument to find()-type methods is depreca661            warnings.warn("The 'text' argument to find()-type methods is depreca
>ted. Use 'string' instead.", DeprecationWarning, stacklevel=_stacklevel)>ted. Use 'string' instead.", DeprecationWarning, stacklevel=_stacklevel)
662        if isinstance(name, SoupStrainer):662        if isinstance(name, SoupStrainer):
663            strainer = name663            strainer = name
664        else:664        else:
665            strainer = SoupStrainer(name, attrs, string, **kwargs)665            strainer = SoupStrainer(name, attrs, string, **kwargs)
666        if string is None and (not limit) and (not attrs) and (not kwargs):666        if string is None and (not limit) and (not attrs) and (not kwargs):
667            if name is True or name is None:667            if name is True or name is None:
668                result = (element for element in generator if isinstance(element668                result = (element for element in generator if isinstance(element
>, Tag))>, Tag))
669                return ResultSet(strainer, result)669                return ResultSet(strainer, result)
670            elif isinstance(name, str):670            elif isinstance(name, str):
671                if name.count(':') == 1:671                if name.count(':') == 1:
672                    (prefix, local_name) = name.split(':', 1)672                    (prefix, local_name) = name.split(':', 1)
673                else:673                else:
674                    prefix = None674                    prefix = None
675                    local_name = name675                    local_name = name
676                result = (element for element in generator if isinstance(element676                result = (element for element in generator if isinstance(element
>, Tag) and element.name == name or (element.name == local_name and (prefix is No>, Tag) and element.name == name or (element.name == local_name and (prefix is No
>ne or element.prefix == prefix)))>ne or element.prefix == prefix)))
677                return ResultSet(strainer, result)677                return ResultSet(strainer, result)
678        results = ResultSet(strainer)678        results = ResultSet(strainer)
679        while True:679        while True:
680            try:680            try:
681                i = next(generator)681                i = next(generator)
682            except StopIteration:682            except StopIteration:
683                break683                break
684            if i:684            if i:
685                found = strainer.search(i)685                found = strainer.search(i)
686                if found:686                if found:
687                    results.append(found)687                    results.append(found)
688                    if limit and len(results) < limit:688                    if limit and len(results) < limit:
689                        break689                        break
690        return results690        return results
691691
692    @property692    @property
693    def next_elements(self):693    def next_elements(self):
694        """All PageElements that were parsed after this one.694        """All PageElements that were parsed after this one.
695695
696        :yield: A sequence of PageElements.696        :yield: A sequence of PageElements.
697        """697        """
698        i = self.next_element698        i = self.next_element
699        while i is not None:699        while i is not None:
700            yield i700            yield i
701            i = i.next_element701            i = i.next_element
702702
703    @property703    @property
704    def next_siblings(self):704    def next_siblings(self):
705        """All PageElements that are siblings of this one but were parsed705        """All PageElements that are siblings of this one but were parsed
706        later.706        later.
707707
708        :yield: A sequence of PageElements.708        :yield: A sequence of PageElements.
709        """709        """
710        i = self.next_sibling710        i = self.next_sibling
711        while i is not None:711        while i is not None:
712            yield i712            yield i
713            i = i.next_sibling713            i = i.next_sibling
714714
715    @property715    @property
716    def previous_elements(self):716    def previous_elements(self):
717        """All PageElements that were parsed before this one.717        """All PageElements that were parsed before this one.
718718
719        :yield: A sequence of PageElements.719        :yield: A sequence of PageElements.
720        """720        """
721        i = self.previous_element721        i = self.previous_element
722        while i is not None:722        while i is not None:
723            yield i723            yield i
724            i = i.previous_element724            i = i.previous_element
725725
726    @property726    @property
727    def previous_siblings(self):727    def previous_siblings(self):
728        """All PageElements that are siblings of this one but were parsed728        """All PageElements that are siblings of this one but were parsed
729        earlier.729        earlier.
730730
731        :yield: A sequence of PageElements.731        :yield: A sequence of PageElements.
732        """732        """
733        i = self.previous_sibling733        i = self.previous_sibling
734        while i is not None:734        while i is not None:
735            yield i735            yield i
736            i = i.previous_sibling736            i = i.previous_sibling
737737
738    @property738    @property
739    def parents(self):739    def parents(self):
740        """All PageElements that are parents of this PageElement.740        """All PageElements that are parents of this PageElement.
741741
742        :yield: A sequence of PageElements.742        :yield: A sequence of PageElements.
743        """743        """
744        i = self.parent744        i = self.parent
745        while i is not None:745        while i is not None:
746            yield i746            yield i
747            i = i.parent747            i = i.parent
748748
749    @property749    @property
750    def decomposed(self):750    def decomposed(self):
751        """Check whether a PageElement has been decomposed.751        """Check whether a PageElement has been decomposed.
752752
753        :rtype: bool753        :rtype: bool
754        """754        """
755        return getattr(self, '_decomposed', False) or False755        return getattr(self, '_decomposed', False) or False
756756
757    def nextGenerator(self):757    def nextGenerator(self):
758        return self.next_elements758        return self.next_elements
759759
760    def nextSiblingGenerator(self):760    def nextSiblingGenerator(self):
761        return self.next_siblings761        return self.next_siblings
762762
763    def previousGenerator(self):763    def previousGenerator(self):
764        return self.previous_elements764        return self.previous_elements
765765
766    def previousSiblingGenerator(self):766    def previousSiblingGenerator(self):
767        return self.previous_siblings767        return self.previous_siblings
768768
769    def parentGenerator(self):769    def parentGenerator(self):
770        return self.parents770        return self.parents
771771
772class NavigableString(str, PageElement):772class NavigableString(str, PageElement):
773    """A Python Unicode string that is part of a parse tree.773    """A Python Unicode string that is part of a parse tree.
774774
775    When Beautiful Soup parses the markup <b>penguin</b>, it will775    When Beautiful Soup parses the markup <b>penguin</b>, it will
776    create a NavigableString for the string "penguin".776    create a NavigableString for the string "penguin".
777    """777    """
778    PREFIX = ''778    PREFIX = ''
779    SUFFIX = ''779    SUFFIX = ''
780    known_xml = None780    known_xml = None
781781
782    def __new__(cls, value):782    def __new__(cls, value):
783        """Create a new NavigableString.783        """Create a new NavigableString.
784784
785        When unpickling a NavigableString, this method is called with785        When unpickling a NavigableString, this method is called with
786        the string in DEFAULT_OUTPUT_ENCODING. That encoding needs to be786        the string in DEFAULT_OUTPUT_ENCODING. That encoding needs to be
787        passed in to the superclass's __new__ or the superclass won't know787        passed in to the superclass's __new__ or the superclass won't know
788        how to handle non-ASCII characters.788        how to handle non-ASCII characters.
789        """789        """
790        if isinstance(value, str):790        if isinstance(value, str):
791            u = str.__new__(cls, value)791            u = str.__new__(cls, value)
792        else:792        else:
793            u = str.__new__(cls, value, DEFAULT_OUTPUT_ENCODING)793            u = str.__new__(cls, value, DEFAULT_OUTPUT_ENCODING)
794        u.setup()794        u.setup()
795        return u795        return u
796796
797    def __copy__(self):797    def __copy__(self):
798        """A copy of a NavigableString has the same contents and class798        """A copy of a NavigableString has the same contents and class
799        as the original, but it is not connected to the parse tree.799        as the original, but it is not connected to the parse tree.
800        """800        """
801        return type(self)(self)801        return type(self)(self)
802802
803    def __getnewargs__(self):803    def __getnewargs__(self):
804        return (str(self),)804        return (str(self),)
805805
806    def __getattr__(self, attr):806    def __getattr__(self, attr):
807        """text.string gives you text. This is for backwards807        """text.string gives you text. This is for backwards
808        compatibility for Navigable*String, but for CData* it lets you808        compatibility for Navigable*String, but for CData* it lets you
809        get the string without the CData wrapper."""809        get the string without the CData wrapper."""
810        if attr == 'string':810        if attr == 'string':
811            return self811            return self
812        else:812        else:
813            raise AttributeError("'%s' object has no attribute '%s'" % (self.__c813            raise AttributeError("'%s' object has no attribute '%s'" % (self.__c
>lass__.__name__, attr))>lass__.__name__, attr))
814814
815    def output_ready(self, formatter='minimal'):815    def output_ready(self, formatter='minimal'):
816        """Run the string through the provided formatter.816        """Run the string through the provided formatter.
817817
818        :param formatter: A Formatter object, or a string naming one of the stan818        :param formatter: A Formatter object, or a string naming one of the stan
>dard formatters.>dard formatters.
819        """819        """
820        output = self.format_string(self, formatter)820        output = self.format_string(self, formatter)
821        return self.PREFIX + output + self.SUFFIX821        return self.PREFIX + output + self.SUFFIX
822822
823    @property823    @property
824    def name(self):824    def name(self):
825        """Since a NavigableString is not a Tag, it has no .name.825        """Since a NavigableString is not a Tag, it has no .name.
826826
827        This property is implemented so that code like this doesn't crash827        This property is implemented so that code like this doesn't crash
828        when run on a mixture of Tag and NavigableString objects:828        when run on a mixture of Tag and NavigableString objects:
829            [x.name for x in tag.children]829            [x.name for x in tag.children]
830        """830        """
831        return None831        return None
832832
833    @name.setter833    @name.setter
834    def name(self, name):834    def name(self, name):
835        """Prevent NavigableString.name from ever being set."""835        """Prevent NavigableString.name from ever being set."""
836        raise AttributeError('A NavigableString cannot be given a name.')836        raise AttributeError('A NavigableString cannot be given a name.')
837837
838    def _all_strings(self, strip=False, types=PageElement.default):838    def _all_strings(self, strip=False, types=PageElement.default):
839        """Yield all strings of certain classes, possibly stripping them.839        """Yield all strings of certain classes, possibly stripping them.
840840
841        This makes it easy for NavigableString to implement methods841        This makes it easy for NavigableString to implement methods
842        like get_text() as conveniences, creating a consistent842        like get_text() as conveniences, creating a consistent
843        text-extraction API across all PageElements.843        text-extraction API across all PageElements.
844844
845        :param strip: If True, all strings will be stripped before being845        :param strip: If True, all strings will be stripped before being
846            yielded.846            yielded.
847847
848        :param types: A tuple of NavigableString subclasses. If this848        :param types: A tuple of NavigableString subclasses. If this
849            NavigableString isn't one of those subclasses, the849            NavigableString isn't one of those subclasses, the
850            sequence will be empty. By default, the subclasses850            sequence will be empty. By default, the subclasses
851            considered are NavigableString and CData objects. That851            considered are NavigableString and CData objects. That
852            means no comments, processing instructions, etc.852            means no comments, processing instructions, etc.
853853
854        :yield: A sequence that either contains this string, or is empty.854        :yield: A sequence that either contains this string, or is empty.
855855
856        """856        """
857        if types is self.default:857        if types is self.default:
858            types = Tag.DEFAULT_INTERESTING_STRING_TYPES858            types = Tag.DEFAULT_INTERESTING_STRING_TYPES
859        my_type = type(self)859        my_type = type(self)
860        if not types is not None:860        if not types is not None:
861            if isinstance(types, type):861            if isinstance(types, type):
862                if my_type is not types:862                if my_type is not types:
863                    return863                    return
864            elif my_type not in types:864            elif my_type not in types:
865                return865                return
866        value = self866        value = self
867        if strip:867        if strip:
868            value = value.strip()868            value = value.strip()
869        if len(value) > 0:869        if len(value) > 0:
870            yield value870            yield value
871    strings = property(_all_strings)871    strings = property(_all_strings)
872872
873class PreformattedString(NavigableString):873class PreformattedString(NavigableString):
874    """A NavigableString not subject to the normal formatting rules.874    """A NavigableString not subject to the normal formatting rules.
875875
876    This is an abstract class used for special kinds of strings such876    This is an abstract class used for special kinds of strings such
877    as comments (the Comment class) and CDATA blocks (the CData877    as comments (the Comment class) and CDATA blocks (the CData
878    class).878    class).
879    """879    """
880    PREFIX = ''880    PREFIX = ''
881    SUFFIX = ''881    SUFFIX = ''
882882
883    def output_ready(self, formatter=None):883    def output_ready(self, formatter=None):
884        """Make this string ready for output by adding any subclass-specific884        """Make this string ready for output by adding any subclass-specific
885            prefix or suffix.885            prefix or suffix.
886886
887        :param formatter: A Formatter object, or a string naming one887        :param formatter: A Formatter object, or a string naming one
888            of the standard formatters. The string will be passed into the888            of the standard formatters. The string will be passed into the
889            Formatter, but only to trigger any side effects: the return889            Formatter, but only to trigger any side effects: the return
890            value is ignored.890            value is ignored.
891891
892        :return: The string, with any subclass-specific prefix and892        :return: The string, with any subclass-specific prefix and
893           suffix added on.893           suffix added on.
894        """894        """
895        if formatter is not None:895        if formatter is not None:
896            ignore = self.format_string(self, formatter)896            ignore = self.format_string(self, formatter)
897        return self.PREFIX + self + self.SUFFIX897        return self.PREFIX + self + self.SUFFIX
898898
899class CData(PreformattedString):899class CData(PreformattedString):
900    """A CDATA block."""900    """A CDATA block."""
901    PREFIX = '<![CDATA['901    PREFIX = '<![CDATA['
902    SUFFIX = ']]>'902    SUFFIX = ']]>'
903903
904class ProcessingInstruction(PreformattedString):904class ProcessingInstruction(PreformattedString):
905    """A SGML processing instruction."""905    """A SGML processing instruction."""
906    PREFIX = '<?'906    PREFIX = '<?'
907    SUFFIX = '>'907    SUFFIX = '>'
908908
909class XMLProcessingInstruction(ProcessingInstruction):909class XMLProcessingInstruction(ProcessingInstruction):
910    """An XML processing instruction."""910    """An XML processing instruction."""
911    PREFIX = '<?'911    PREFIX = '<?'
912    SUFFIX = '?>'912    SUFFIX = '?>'
913913
914class Comment(PreformattedString):914class Comment(PreformattedString):
915    """An HTML or XML comment."""915    """An HTML or XML comment."""
916    PREFIX = '<!--'916    PREFIX = '<!--'
917    SUFFIX = '-->'917    SUFFIX = '-->'
918918
919class Declaration(PreformattedString):919class Declaration(PreformattedString):
920    """An XML declaration."""920    """An XML declaration."""
921    PREFIX = '<?'921    PREFIX = '<?'
922    SUFFIX = '?>'922    SUFFIX = '?>'
923923
924class Doctype(PreformattedString):924class Doctype(PreformattedString):
925    """A document type declaration."""925    """A document type declaration."""
926926
927    @classmethod927    @classmethod
928    def for_name_and_ids(cls, name, pub_id, system_id):928    def for_name_and_ids(cls, name, pub_id, system_id):
929        """Generate an appropriate document type declaration for a given929        """Generate an appropriate document type declaration for a given
930        public ID and system ID.930        public ID and system ID.
931931
932        :param name: The name of the document's root element, e.g. 'html'.932        :param name: The name of the document's root element, e.g. 'html'.
933        :param pub_id: The Formal Public Identifier for this document type,933        :param pub_id: The Formal Public Identifier for this document type,
934            e.g. '-//W3C//DTD XHTML 1.1//EN'934            e.g. '-//W3C//DTD XHTML 1.1//EN'
935        :param system_id: The system identifier for this document type,935        :param system_id: The system identifier for this document type,
936            e.g. 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'936            e.g. 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'
937937
938        :return: A Doctype.938        :return: A Doctype.
939        """939        """
940        value = name or ''940        value = name or ''
941        if pub_id is not None:941        if pub_id is not None:
942            value += ' PUBLIC "%s"' % pub_id942            value += ' PUBLIC "%s"' % pub_id
943            if system_id is not None:943            if system_id is not None:
944                value += ' "%s"' % system_id944                value += ' "%s"' % system_id
945        elif system_id is not None:945        elif system_id is not None:
n946            value += ' SYSTEM "%s"' % system_idn946            value -= ' SYSTEM "%s"' % system_id
947        return Doctype(value)947        return Doctype(value)
948    PREFIX = '<!DOCTYPE '948    PREFIX = '<!DOCTYPE '
949    SUFFIX = '>\n'949    SUFFIX = '>\n'
950950
951class Stylesheet(NavigableString):951class Stylesheet(NavigableString):
952    """A NavigableString representing an stylesheet (probably952    """A NavigableString representing an stylesheet (probably
953    CSS).953    CSS).
954954
955    Used to distinguish embedded stylesheets from textual content.955    Used to distinguish embedded stylesheets from textual content.
956    """956    """
957    pass957    pass
958958
959class Script(NavigableString):959class Script(NavigableString):
960    """A NavigableString representing an executable script (probably960    """A NavigableString representing an executable script (probably
961    Javascript).961    Javascript).
962962
963    Used to distinguish executable code from textual content.963    Used to distinguish executable code from textual content.
964    """964    """
965    pass965    pass
966966
967class TemplateString(NavigableString):967class TemplateString(NavigableString):
968    """A NavigableString representing a string found inside an HTML968    """A NavigableString representing a string found inside an HTML
969    template embedded in a larger document.969    template embedded in a larger document.
970970
971    Used to distinguish such strings from the main body of the document.971    Used to distinguish such strings from the main body of the document.
972    """972    """
973    pass973    pass
974974
975class RubyTextString(NavigableString):975class RubyTextString(NavigableString):
976    """A NavigableString representing the contents of the <rt> HTML976    """A NavigableString representing the contents of the <rt> HTML
977    element.977    element.
978978
979    https://dev.w3.org/html5/spec-LC/text-level-semantics.html#the-rt-element979    https://dev.w3.org/html5/spec-LC/text-level-semantics.html#the-rt-element
980980
981    Can be used to distinguish such strings from the strings they're981    Can be used to distinguish such strings from the strings they're
982    annotating.982    annotating.
983    """983    """
984    pass984    pass
985985
986class RubyParenthesisString(NavigableString):986class RubyParenthesisString(NavigableString):
987    """A NavigableString representing the contents of the <rp> HTML987    """A NavigableString representing the contents of the <rp> HTML
988    element.988    element.
989989
990    https://dev.w3.org/html5/spec-LC/text-level-semantics.html#the-rp-element990    https://dev.w3.org/html5/spec-LC/text-level-semantics.html#the-rp-element
991    """991    """
992    pass992    pass
993993
994class Tag(PageElement):994class Tag(PageElement):
995    """Represents an HTML or XML tag that is part of a parse tree, along995    """Represents an HTML or XML tag that is part of a parse tree, along
996    with its attributes and contents.996    with its attributes and contents.
997997
998    When Beautiful Soup parses the markup <b>penguin</b>, it will998    When Beautiful Soup parses the markup <b>penguin</b>, it will
999    create a Tag object representing the <b> tag.999    create a Tag object representing the <b> tag.
1000    """1000    """
10011001
1002    def __init__(self, parser=None, builder=None, name=None, namespace=None, pre1002    def __init__(self, parser=None, builder=None, name=None, namespace=None, pre
>fix=None, attrs=None, parent=None, previous=None, is_xml=None, sourceline=None, >fix=None, attrs=None, parent=None, previous=None, is_xml=None, sourceline=None, 
>sourcepos=None, can_be_empty_element=None, cdata_list_attributes=None, preserve_>sourcepos=None, can_be_empty_element=None, cdata_list_attributes=None, preserve_
>whitespace_tags=None, interesting_string_types=None, namespaces=None):>whitespace_tags=None, interesting_string_types=None, namespaces=None):
1003        """Basic constructor.1003        """Basic constructor.
10041004
1005        :param parser: A BeautifulSoup object.1005        :param parser: A BeautifulSoup object.
1006        :param builder: A TreeBuilder.1006        :param builder: A TreeBuilder.
1007        :param name: The name of the tag.1007        :param name: The name of the tag.
1008        :param namespace: The URI of this Tag's XML namespace, if any.1008        :param namespace: The URI of this Tag's XML namespace, if any.
1009        :param prefix: The prefix for this Tag's XML namespace, if any.1009        :param prefix: The prefix for this Tag's XML namespace, if any.
1010        :param attrs: A dictionary of this Tag's attribute values.1010        :param attrs: A dictionary of this Tag's attribute values.
1011        :param parent: The PageElement to use as this Tag's parent.1011        :param parent: The PageElement to use as this Tag's parent.
1012        :param previous: The PageElement that was parsed immediately before1012        :param previous: The PageElement that was parsed immediately before
1013            this tag.1013            this tag.
1014        :param is_xml: If True, this is an XML tag. Otherwise, this is an1014        :param is_xml: If True, this is an XML tag. Otherwise, this is an
1015            HTML tag.1015            HTML tag.
1016        :param sourceline: The line number where this tag was found in its1016        :param sourceline: The line number where this tag was found in its
1017            source document.1017            source document.
1018        :param sourcepos: The character position within `sourceline` where this1018        :param sourcepos: The character position within `sourceline` where this
1019            tag was found.1019            tag was found.
1020        :param can_be_empty_element: If True, this tag should be1020        :param can_be_empty_element: If True, this tag should be
1021            represented as <tag/>. If False, this tag should be represented1021            represented as <tag/>. If False, this tag should be represented
1022            as <tag></tag>.1022            as <tag></tag>.
1023        :param cdata_list_attributes: A list of attributes whose values should1023        :param cdata_list_attributes: A list of attributes whose values should
1024            be treated as CDATA if they ever show up on this tag.1024            be treated as CDATA if they ever show up on this tag.
1025        :param preserve_whitespace_tags: A list of tag names whose contents1025        :param preserve_whitespace_tags: A list of tag names whose contents
1026            should have their whitespace preserved.1026            should have their whitespace preserved.
1027        :param interesting_string_types: This is a NavigableString1027        :param interesting_string_types: This is a NavigableString
1028            subclass or a tuple of them. When iterating over this1028            subclass or a tuple of them. When iterating over this
1029            Tag's strings in methods like Tag.strings or Tag.get_text,1029            Tag's strings in methods like Tag.strings or Tag.get_text,
1030            these are the types of strings that are interesting enough1030            these are the types of strings that are interesting enough
1031            to be considered. The default is to consider1031            to be considered. The default is to consider
1032            NavigableString and CData the only interesting string1032            NavigableString and CData the only interesting string
1033            subtypes.1033            subtypes.
1034        :param namespaces: A dictionary mapping currently active1034        :param namespaces: A dictionary mapping currently active
1035            namespace prefixes to URIs. This can be used later to1035            namespace prefixes to URIs. This can be used later to
1036            construct CSS selectors.1036            construct CSS selectors.
1037        """1037        """
1038        if parser is None:1038        if parser is None:
1039            self.parser_class = None1039            self.parser_class = None
1040        else:1040        else:
1041            self.parser_class = parser.__class__1041            self.parser_class = parser.__class__
1042        if name is None:1042        if name is None:
1043            raise ValueError("No value provided for new tag's name.")1043            raise ValueError("No value provided for new tag's name.")
1044        self.name = name1044        self.name = name
1045        self.namespace = namespace1045        self.namespace = namespace
1046        self._namespaces = namespaces or {}1046        self._namespaces = namespaces or {}
1047        self.prefix = prefix1047        self.prefix = prefix
1048        if (not builder or builder.store_line_numbers) and (sourceline is not No1048        if (not builder or builder.store_line_numbers) and (sourceline is not No
>ne or sourcepos is not None):>ne or sourcepos is not None):
1049            self.sourceline = sourceline1049            self.sourceline = sourceline
1050            self.sourcepos = sourcepos1050            self.sourcepos = sourcepos
1051        if attrs is None:1051        if attrs is None:
1052            attrs = {}1052            attrs = {}
1053        elif attrs:1053        elif attrs:
1054            if builder is not None and builder.cdata_list_attributes:1054            if builder is not None and builder.cdata_list_attributes:
1055                attrs = builder._replace_cdata_list_attribute_values(self.name, 1055                attrs = builder._replace_cdata_list_attribute_values(self.name, 
>attrs)>attrs)
1056            else:1056            else:
1057                attrs = dict(attrs)1057                attrs = dict(attrs)
1058        else:1058        else:
1059            attrs = dict(attrs)1059            attrs = dict(attrs)
1060        if builder:1060        if builder:
1061            self.known_xml = builder.is_xml1061            self.known_xml = builder.is_xml
1062        else:1062        else:
1063            self.known_xml = is_xml1063            self.known_xml = is_xml
1064        self.attrs = attrs1064        self.attrs = attrs
1065        self.contents = []1065        self.contents = []
1066        self.setup(parent, previous)1066        self.setup(parent, previous)
1067        self.hidden = False1067        self.hidden = False
1068        if builder is None:1068        if builder is None:
1069            self.can_be_empty_element = can_be_empty_element1069            self.can_be_empty_element = can_be_empty_element
1070            self.cdata_list_attributes = cdata_list_attributes1070            self.cdata_list_attributes = cdata_list_attributes
1071            self.preserve_whitespace_tags = preserve_whitespace_tags1071            self.preserve_whitespace_tags = preserve_whitespace_tags
1072            self.interesting_string_types = interesting_string_types1072            self.interesting_string_types = interesting_string_types
1073        else:1073        else:
1074            builder.set_up_substitutions(self)1074            builder.set_up_substitutions(self)
1075            self.can_be_empty_element = builder.can_be_empty_element(name)1075            self.can_be_empty_element = builder.can_be_empty_element(name)
1076            self.cdata_list_attributes = builder.cdata_list_attributes1076            self.cdata_list_attributes = builder.cdata_list_attributes
1077            self.preserve_whitespace_tags = builder.preserve_whitespace_tags1077            self.preserve_whitespace_tags = builder.preserve_whitespace_tags
1078            if self.name in builder.string_containers:1078            if self.name in builder.string_containers:
1079                self.interesting_string_types = builder.string_containers[self.n1079                self.interesting_string_types = builder.string_containers[self.n
>ame]>ame]
1080            else:1080            else:
1081                self.interesting_string_types = self.DEFAULT_INTERESTING_STRING_1081                self.interesting_string_types = self.DEFAULT_INTERESTING_STRING_
>TYPES>TYPES
1082    parserClass = _alias('parser_class')1082    parserClass = _alias('parser_class')
10831083
1084    def __copy__(self):1084    def __copy__(self):
1085        """A copy of a Tag is a new Tag, unconnected to the parse tree.1085        """A copy of a Tag is a new Tag, unconnected to the parse tree.
1086        Its contents are a copy of the old Tag's contents.1086        Its contents are a copy of the old Tag's contents.
1087        """1087        """
1088        clone = type(self)(None, self.builder, self.name, self.namespace, self.p1088        clone = type(self)(None, self.builder, self.name, self.namespace, self.p
>refix, self.attrs, is_xml=self._is_xml, sourceline=self.sourceline, sourcepos=se>refix, self.attrs, is_xml=self._is_xml, sourceline=self.sourceline, sourcepos=se
>lf.sourcepos, can_be_empty_element=self.can_be_empty_element, cdata_list_attribu>lf.sourcepos, can_be_empty_element=self.can_be_empty_element, cdata_list_attribu
>tes=self.cdata_list_attributes, preserve_whitespace_tags=self.preserve_whitespac>tes=self.cdata_list_attributes, preserve_whitespace_tags=self.preserve_whitespac
>e_tags, interesting_string_types=self.interesting_string_types)>e_tags, interesting_string_types=self.interesting_string_types)
1089        for attr in ('can_be_empty_element', 'hidden'):1089        for attr in ('can_be_empty_element', 'hidden'):
1090            setattr(clone, attr, getattr(self, attr))1090            setattr(clone, attr, getattr(self, attr))
1091        for child in self.contents:1091        for child in self.contents:
1092            clone.append(child.__copy__())1092            clone.append(child.__copy__())
1093        return clone1093        return clone
10941094
1095    @property1095    @property
1096    def is_empty_element(self):1096    def is_empty_element(self):
1097        """Is this tag an empty-element tag? (aka a self-closing tag)1097        """Is this tag an empty-element tag? (aka a self-closing tag)
10981098
1099        A tag that has contents is never an empty-element tag.1099        A tag that has contents is never an empty-element tag.
11001100
1101        A tag that has no contents may or may not be an empty-element1101        A tag that has no contents may or may not be an empty-element
1102        tag. It depends on the builder used to create the tag. If the1102        tag. It depends on the builder used to create the tag. If the
1103        builder has a designated list of empty-element tags, then only1103        builder has a designated list of empty-element tags, then only
1104        a tag whose name shows up in that list is considered an1104        a tag whose name shows up in that list is considered an
1105        empty-element tag.1105        empty-element tag.
11061106
1107        If the builder has no designated list of empty-element tags,1107        If the builder has no designated list of empty-element tags,
1108        then any tag with no contents is an empty-element tag.1108        then any tag with no contents is an empty-element tag.
1109        """1109        """
1110        return len(self.contents) == 0 and self.can_be_empty_element1110        return len(self.contents) == 0 and self.can_be_empty_element
1111    isSelfClosing = is_empty_element1111    isSelfClosing = is_empty_element
11121112
1113    @property1113    @property
1114    def string(self):1114    def string(self):
1115        """Convenience property to get the single string within this1115        """Convenience property to get the single string within this
1116        PageElement.1116        PageElement.
11171117
1118        TODO It might make sense to have NavigableString.string return1118        TODO It might make sense to have NavigableString.string return
1119        itself.1119        itself.
11201120
1121        :return: If this element has a single string child, return1121        :return: If this element has a single string child, return
1122         value is that string. If this element has one child tag,1122         value is that string. If this element has one child tag,
1123         return value is the 'string' attribute of the child tag,1123         return value is the 'string' attribute of the child tag,
1124         recursively. If this element is itself a string, has no1124         recursively. If this element is itself a string, has no
1125         children, or has more than one child, return value is None.1125         children, or has more than one child, return value is None.
1126        """1126        """
1127        if len(self.contents) != 1:1127        if len(self.contents) != 1:
1128            return None1128            return None
1129        child = self.contents[0]1129        child = self.contents[0]
1130        if isinstance(child, NavigableString):1130        if isinstance(child, NavigableString):
1131            return child1131            return child
1132        return child.string1132        return child.string
11331133
1134    @string.setter1134    @string.setter
1135    def string(self, string):1135    def string(self, string):
1136        """Replace this PageElement's contents with `string`."""1136        """Replace this PageElement's contents with `string`."""
1137        self.clear()1137        self.clear()
1138        self.append(string.__class__(string))1138        self.append(string.__class__(string))
1139    DEFAULT_INTERESTING_STRING_TYPES = (NavigableString, CData)1139    DEFAULT_INTERESTING_STRING_TYPES = (NavigableString, CData)
11401140
1141    def _all_strings(self, strip=False, types=PageElement.default):1141    def _all_strings(self, strip=False, types=PageElement.default):
1142        """Yield all strings of certain classes, possibly stripping them.1142        """Yield all strings of certain classes, possibly stripping them.
11431143
1144        :param strip: If True, all strings will be stripped before being1144        :param strip: If True, all strings will be stripped before being
1145            yielded.1145            yielded.
11461146
1147        :param types: A tuple of NavigableString subclasses. Any strings of1147        :param types: A tuple of NavigableString subclasses. Any strings of
1148            a subclass not found in this list will be ignored. By1148            a subclass not found in this list will be ignored. By
1149            default, the subclasses considered are the ones found in1149            default, the subclasses considered are the ones found in
1150            self.interesting_string_types. If that's not specified,1150            self.interesting_string_types. If that's not specified,
1151            only NavigableString and CData objects will be1151            only NavigableString and CData objects will be
1152            considered. That means no comments, processing1152            considered. That means no comments, processing
1153            instructions, etc.1153            instructions, etc.
11541154
1155        :yield: A sequence of strings.1155        :yield: A sequence of strings.
11561156
1157        """1157        """
1158        if types is self.default:1158        if types is self.default:
1159            types = self.interesting_string_types1159            types = self.interesting_string_types
1160        for descendant in self.descendants:1160        for descendant in self.descendants:
1161            if types is None and (not isinstance(descendant, NavigableString)):1161            if types is None and (not isinstance(descendant, NavigableString)):
1162                continue1162                continue
1163            descendant_type = type(descendant)1163            descendant_type = type(descendant)
1164            if isinstance(types, type):1164            if isinstance(types, type):
1165                if descendant_type is not types:1165                if descendant_type is not types:
1166                    continue1166                    continue
1167            elif types is not None and descendant_type not in types:1167            elif types is not None and descendant_type not in types:
1168                continue1168                continue
1169            if strip:1169            if strip:
1170                descendant = descendant.strip()1170                descendant = descendant.strip()
1171                if len(descendant) == 0:1171                if len(descendant) == 0:
1172                    continue1172                    continue
1173            yield descendant1173            yield descendant
1174    strings = property(_all_strings)1174    strings = property(_all_strings)
11751175
1176    def decompose(self):1176    def decompose(self):
1177        """Recursively destroys this PageElement and its children.1177        """Recursively destroys this PageElement and its children.
11781178
1179        This element will be removed from the tree and wiped out; so1179        This element will be removed from the tree and wiped out; so
1180        will everything beneath it.1180        will everything beneath it.
11811181
1182        The behavior of a decomposed PageElement is undefined and you1182        The behavior of a decomposed PageElement is undefined and you
1183        should never use one for anything, but if you need to _check_1183        should never use one for anything, but if you need to _check_
1184        whether an element has been decomposed, you can use the1184        whether an element has been decomposed, you can use the
1185        `decomposed` property.1185        `decomposed` property.
1186        """1186        """
1187        self.extract()1187        self.extract()
1188        i = self1188        i = self
1189        while i is not None:1189        while i is not None:
1190            n = i.next_element1190            n = i.next_element
1191            i.__dict__.clear()1191            i.__dict__.clear()
1192            i.contents = []1192            i.contents = []
1193            i._decomposed = True1193            i._decomposed = True
1194            i = n1194            i = n
11951195
1196    def clear(self, decompose=False):1196    def clear(self, decompose=False):
1197        """Wipe out all children of this PageElement by calling extract()1197        """Wipe out all children of this PageElement by calling extract()
1198           on them.1198           on them.
11991199
1200        :param decompose: If this is True, decompose() (a more1200        :param decompose: If this is True, decompose() (a more
1201            destructive method) will be called instead of extract().1201            destructive method) will be called instead of extract().
1202        """1202        """
1203        if decompose:1203        if decompose:
1204            for element in self.contents[:]:1204            for element in self.contents[:]:
1205                if isinstance(element, Tag):1205                if isinstance(element, Tag):
1206                    element.decompose()1206                    element.decompose()
1207                else:1207                else:
1208                    element.extract()1208                    element.extract()
1209        else:1209        else:
1210            for element in self.contents[:]:1210            for element in self.contents[:]:
1211                element.extract()1211                element.extract()
12121212
1213    def smooth(self):1213    def smooth(self):
1214        """Smooth out this element's children by consolidating consecutive1214        """Smooth out this element's children by consolidating consecutive
1215        strings.1215        strings.
12161216
1217        This makes pretty-printed output look more natural following a1217        This makes pretty-printed output look more natural following a
1218        lot of operations that modified the tree.1218        lot of operations that modified the tree.
1219        """1219        """
1220        marked = []1220        marked = []
1221        for (i, a) in enumerate(self.contents):1221        for (i, a) in enumerate(self.contents):
1222            if isinstance(a, Tag):1222            if isinstance(a, Tag):
1223                a.smooth()1223                a.smooth()
1224            if i == len(self.contents) - 1:1224            if i == len(self.contents) - 1:
1225                continue1225                continue
1226            b = self.contents[i + 1]1226            b = self.contents[i + 1]
1227            if isinstance(a, NavigableString) and isinstance(b, NavigableString)1227            if isinstance(a, NavigableString) and isinstance(b, NavigableString)
> and (not isinstance(a, PreformattedString)) and (not isinstance(b, Preformatted> and (not isinstance(a, PreformattedString)) and (not isinstance(b, Preformatted
>String)):>String)):
1228                marked.append(i)1228                marked.append(i)
1229        for i in reversed(marked):1229        for i in reversed(marked):
1230            a = self.contents[i]1230            a = self.contents[i]
1231            b = self.contents[i + 1]1231            b = self.contents[i + 1]
1232            b.extract()1232            b.extract()
1233            n = NavigableString(a + b)1233            n = NavigableString(a + b)
1234            a.replace_with(n)1234            a.replace_with(n)
12351235
1236    def index(self, element):1236    def index(self, element):
1237        """Find the index of a child by identity, not value.1237        """Find the index of a child by identity, not value.
12381238
1239        Avoids issues with tag.contents.index(element) getting the1239        Avoids issues with tag.contents.index(element) getting the
1240        index of equal elements.1240        index of equal elements.
12411241
1242        :param element: Look for this PageElement in `self.contents`.1242        :param element: Look for this PageElement in `self.contents`.
1243        """1243        """
1244        for (i, child) in enumerate(self.contents):1244        for (i, child) in enumerate(self.contents):
1245            if child is element:1245            if child is element:
1246                return i1246                return i
1247        raise ValueError('Tag.index: element not in tag')1247        raise ValueError('Tag.index: element not in tag')
12481248
1249    def get(self, key, default=None):1249    def get(self, key, default=None):
1250        """Returns the value of the 'key' attribute for the tag, or1250        """Returns the value of the 'key' attribute for the tag, or
1251        the value given for 'default' if it doesn't have that1251        the value given for 'default' if it doesn't have that
1252        attribute."""1252        attribute."""
1253        return self.attrs.get(key, default)1253        return self.attrs.get(key, default)
12541254
1255    def get_attribute_list(self, key, default=None):1255    def get_attribute_list(self, key, default=None):
1256        """The same as get(), but always returns a list.1256        """The same as get(), but always returns a list.
12571257
1258        :param key: The attribute to look for.1258        :param key: The attribute to look for.
1259        :param default: Use this value if the attribute is not present1259        :param default: Use this value if the attribute is not present
1260            on this PageElement.1260            on this PageElement.
1261        :return: A list of values, probably containing only a single1261        :return: A list of values, probably containing only a single
1262            value.1262            value.
1263        """1263        """
1264        value = self.get(key, default)1264        value = self.get(key, default)
1265        if not isinstance(value, list):1265        if not isinstance(value, list):
1266            value = [value]1266            value = [value]
1267        return1267        return
12681268
1269    def has_attr(self, key):1269    def has_attr(self, key):
1270        """Does this PageElement have an attribute with the given name?"""1270        """Does this PageElement have an attribute with the given name?"""
1271        return key in self.attrs1271        return key in self.attrs
12721272
1273    def __hash__(self):1273    def __hash__(self):
1274        return str(self).__hash__()1274        return str(self).__hash__()
12751275
1276    def __getitem__(self, key):1276    def __getitem__(self, key):
1277        """tag[key] returns the value of the 'key' attribute for the Tag,1277        """tag[key] returns the value of the 'key' attribute for the Tag,
1278        and throws an exception if it's not there."""1278        and throws an exception if it's not there."""
1279        return self.attrs[key]1279        return self.attrs[key]
12801280
1281    def __iter__(self):1281    def __iter__(self):
1282        """Iterating over a Tag iterates over its contents."""1282        """Iterating over a Tag iterates over its contents."""
1283        return iter(self.contents)1283        return iter(self.contents)
12841284
1285    def __len__(self):1285    def __len__(self):
1286        """The length of a Tag is the length of its list of contents."""1286        """The length of a Tag is the length of its list of contents."""
1287        return len(self.contents)1287        return len(self.contents)
12881288
1289    def __contains__(self, x):1289    def __contains__(self, x):
1290        return x in self.contents1290        return x in self.contents
12911291
1292    def __bool__(self):1292    def __bool__(self):
1293        """A tag is non-None even if it has no contents."""1293        """A tag is non-None even if it has no contents."""
1294        return True1294        return True
12951295
1296    def __setitem__(self, key, value):1296    def __setitem__(self, key, value):
1297        """Setting tag[key] sets the value of the 'key' attribute for the1297        """Setting tag[key] sets the value of the 'key' attribute for the
1298        tag."""1298        tag."""
1299        self.attrs[key] = value1299        self.attrs[key] = value
13001300
1301    def __delitem__(self, key):1301    def __delitem__(self, key):
1302        """Deleting tag[key] deletes all 'key' attributes for the tag."""1302        """Deleting tag[key] deletes all 'key' attributes for the tag."""
1303        self.attrs.pop(key, None)1303        self.attrs.pop(key, None)
13041304
1305    def __call__(self, *args, **kwargs):1305    def __call__(self, *args, **kwargs):
1306        """Calling a Tag like a function is the same as calling its1306        """Calling a Tag like a function is the same as calling its
1307        find_all() method. Eg. tag('a') returns a list of all the A tags1307        find_all() method. Eg. tag('a') returns a list of all the A tags
1308        found within this tag."""1308        found within this tag."""
1309        return self.find_all(*args, **kwargs)1309        return self.find_all(*args, **kwargs)
13101310
1311    def __getattr__(self, tag):1311    def __getattr__(self, tag):
1312        """Calling tag.subtag is the same as calling tag.find(name="subtag")"""1312        """Calling tag.subtag is the same as calling tag.find(name="subtag")"""
1313        if len(tag) > 3 and tag.endswith('Tag'):1313        if len(tag) > 3 and tag.endswith('Tag'):
1314            tag_name = tag[:-3]1314            tag_name = tag[:-3]
1315            warnings.warn('.%(name)sTag is deprecated, use .find("%(name)s") ins1315            warnings.warn('.%(name)sTag is deprecated, use .find("%(name)s") ins
>tead. If you really were looking for a tag called %(name)sTag, use .find("%(name>tead. If you really were looking for a tag called %(name)sTag, use .find("%(name
>)sTag")' % dict(name=tag_name), DeprecationWarning, stacklevel=2)>)sTag")' % dict(name=tag_name), DeprecationWarning, stacklevel=2)
1316            return self.find(tag_name)1316            return self.find(tag_name)
1317        elif not tag.startswith('__') and (not tag == 'contents'):1317        elif not tag.startswith('__') and (not tag == 'contents'):
1318            return self.find(tag)1318            return self.find(tag)
1319        raise AttributeError("'%s' object has no attribute '%s'" % (self.__class1319        raise AttributeError("'%s' object has no attribute '%s'" % (self.__class
>__, tag))>__, tag))
13201320
1321    def __eq__(self, other):1321    def __eq__(self, other):
1322        """Returns true iff this Tag has the same name, the same attributes,1322        """Returns true iff this Tag has the same name, the same attributes,
1323        and the same contents (recursively) as `other`."""1323        and the same contents (recursively) as `other`."""
1324        if self is other:1324        if self is other:
1325            return True1325            return True
1326        if not hasattr(other, 'name') or not hasattr(other, 'attrs') or (not has1326        if not hasattr(other, 'name') or not hasattr(other, 'attrs') or (not has
>attr(other, 'contents')) or (self.name != other.name) or (self.attrs != other.at>attr(other, 'contents')) or (self.name != other.name) or (self.attrs != other.at
>trs) or (len(self) != len(other)):>trs) or (len(self) != len(other)):
1327            return False1327            return False
1328        for (i, my_child) in enumerate(self.contents):1328        for (i, my_child) in enumerate(self.contents):
1329            if my_child != other.contents[i]:1329            if my_child != other.contents[i]:
1330                return False1330                return False
1331        return True1331        return True
13321332
1333    def __ne__(self, other):1333    def __ne__(self, other):
1334        """Returns true iff this Tag is not identical to `other`,1334        """Returns true iff this Tag is not identical to `other`,
1335        as defined in __eq__."""1335        as defined in __eq__."""
1336        return not self == other1336        return not self == other
13371337
1338    def __repr__(self, encoding='unicode-escape'):1338    def __repr__(self, encoding='unicode-escape'):
1339        """Renders this PageElement as a string.1339        """Renders this PageElement as a string.
13401340
1341        :param encoding: The encoding to use (Python 2 only).1341        :param encoding: The encoding to use (Python 2 only).
1342            TODO: This is now ignored and a warning should be issued1342            TODO: This is now ignored and a warning should be issued
1343            if a value is provided.1343            if a value is provided.
1344        :return: A (Unicode) string.1344        :return: A (Unicode) string.
1345        """1345        """
1346        return self.decode()1346        return self.decode()
13471347
1348    def __unicode__(self):1348    def __unicode__(self):
1349        """Renders this PageElement as a Unicode string."""1349        """Renders this PageElement as a Unicode string."""
1350        return self.decode()1350        return self.decode()
1351    __str__ = __repr__ = __unicode__1351    __str__ = __repr__ = __unicode__
13521352
1353    def encode(self, encoding=DEFAULT_OUTPUT_ENCODING, indent_level=None, format1353    def encode(self, encoding=DEFAULT_OUTPUT_ENCODING, indent_level=None, format
>ter='minimal', errors='xmlcharrefreplace'):>ter='minimal', errors='xmlcharrefreplace'):
1354        """Render a bytestring representation of this PageElement and its1354        """Render a bytestring representation of this PageElement and its
1355        contents.1355        contents.
13561356
1357        :param encoding: The destination encoding.1357        :param encoding: The destination encoding.
1358        :param indent_level: Each line of the rendering will be1358        :param indent_level: Each line of the rendering will be
1359           indented this many levels. (The formatter decides what a1359           indented this many levels. (The formatter decides what a
1360           'level' means in terms of spaces or other characters1360           'level' means in terms of spaces or other characters
1361           output.) Used internally in recursive calls while1361           output.) Used internally in recursive calls while
1362           pretty-printing.1362           pretty-printing.
1363        :param formatter: A Formatter object, or a string naming one of1363        :param formatter: A Formatter object, or a string naming one of
1364            the standard formatters.1364            the standard formatters.
1365        :param errors: An error handling strategy such as1365        :param errors: An error handling strategy such as
1366            'xmlcharrefreplace'. This value is passed along into1366            'xmlcharrefreplace'. This value is passed along into
1367            encode() and its value should be one of the constants1367            encode() and its value should be one of the constants
1368            defined by Python.1368            defined by Python.
1369        :return: A bytestring.1369        :return: A bytestring.
13701370
1371        """1371        """
1372        u = self.decode(indent_level, encoding, formatter)1372        u = self.decode(indent_level, encoding, formatter)
1373        return u.encode(encoding, errors)1373        return u.encode(encoding, errors)
13741374
1375    def decode(self, indent_level=None, eventual_encoding=DEFAULT_OUTPUT_ENCODIN1375    def decode(self, indent_level=None, eventual_encoding=DEFAULT_OUTPUT_ENCODIN
>G, formatter='minimal'):>G, formatter='minimal'):
1376        """Render a Unicode representation of this PageElement and its1376        """Render a Unicode representation of this PageElement and its
1377        contents.1377        contents.
13781378
1379        :param indent_level: Each line of the rendering will be1379        :param indent_level: Each line of the rendering will be
1380             indented this many spaces. Used internally in1380             indented this many spaces. Used internally in
1381             recursive calls while pretty-printing.1381             recursive calls while pretty-printing.
1382        :param eventual_encoding: The tag is destined to be1382        :param eventual_encoding: The tag is destined to be
1383            encoded into this encoding. This method is _not_1383            encoded into this encoding. This method is _not_
1384            responsible for performing that encoding. This information1384            responsible for performing that encoding. This information
1385            is passed in so that it can be substituted in if the1385            is passed in so that it can be substituted in if the
1386            document contains a <META> tag that mentions the document's1386            document contains a <META> tag that mentions the document's
1387            encoding.1387            encoding.
1388        :param formatter: A Formatter object, or a string naming one of1388        :param formatter: A Formatter object, or a string naming one of
1389            the standard formatters.1389            the standard formatters.
1390        """1390        """
1391        if not isinstance(formatter, Formatter):1391        if not isinstance(formatter, Formatter):
1392            formatter = self.formatter_for_name(formatter)1392            formatter = self.formatter_for_name(formatter)
1393        attributes = formatter.attributes(self)1393        attributes = formatter.attributes(self)
1394        attrs = []1394        attrs = []
1395        for (key, val) in attributes:1395        for (key, val) in attributes:
1396            if val is None:1396            if val is None:
1397                decoded = key1397                decoded = key
1398            else:1398            else:
1399                if isinstance(val, list) or isinstance(val, tuple):1399                if isinstance(val, list) or isinstance(val, tuple):
1400                    val = ' '.join(val)1400                    val = ' '.join(val)
1401                elif not isinstance(val, str):1401                elif not isinstance(val, str):
1402                    val = str(val)1402                    val = str(val)
1403                elif isinstance(val, AttributeValueWithCharsetSubstitution) and 1403                elif isinstance(val, AttributeValueWithCharsetSubstitution) and 
>eventual_encoding is not None:>eventual_encoding is not None:
1404                    val = val.encode(eventual_encoding)1404                    val = val.encode(eventual_encoding)
1405                text = formatter.attribute_value(val)1405                text = formatter.attribute_value(val)
1406                decoded = str(key) + '=' + formatter.quoted_attribute_value(text1406                decoded = str(key) + '=' + formatter.quoted_attribute_value(text
>)>)
1407            attrs.append(decoded)1407            attrs.append(decoded)
1408        close = ''1408        close = ''
1409        closeTag = ''1409        closeTag = ''
1410        prefix = ''1410        prefix = ''
1411        if self.prefix:1411        if self.prefix:
1412            prefix = self.prefix + ':'1412            prefix = self.prefix + ':'
1413        if self.is_empty_element:1413        if self.is_empty_element:
1414            close = formatter.void_element_close_prefix or ''1414            close = formatter.void_element_close_prefix or ''
1415        else:1415        else:
1416            closeTag = '</%s%s>' % (prefix, self.name)1416            closeTag = '</%s%s>' % (prefix, self.name)
1417        pretty_print = self._should_pretty_print(indent_level)1417        pretty_print = self._should_pretty_print(indent_level)
1418        space = ''1418        space = ''
1419        indent_space = ''1419        indent_space = ''
1420        if indent_level is not None:1420        if indent_level is not None:
1421            indent_space = formatter.indent * (indent_level + 1)1421            indent_space = formatter.indent * (indent_level + 1)
1422        if pretty_print:1422        if pretty_print:
1423            space = indent_space1423            space = indent_space
1424            indent_contents = indent_level + 11424            indent_contents = indent_level + 1
1425        else:1425        else:
1426            indent_contents = None1426            indent_contents = None
1427        contents = self.decode_contents(indent_contents, eventual_encoding, form1427        contents = self.decode_contents(indent_contents, eventual_encoding, form
>atter)>atter)
1428        if self.hidden:1428        if self.hidden:
1429            s = contents1429            s = contents
1430        else:1430        else:
1431            s = []1431            s = []
1432            attribute_string = ''1432            attribute_string = ''
1433            if attrs:1433            if attrs:
1434                attribute_string = ' ' + ' '.join(attrs)1434                attribute_string = ' ' + ' '.join(attrs)
1435            if indent_level is not None:1435            if indent_level is not None:
1436                s.append(indent_space)1436                s.append(indent_space)
1437            s.append('<%s%s%s%s>' % (prefix, self.name, attribute_string, close)1437            s.append('<%s%s%s%s>' % (prefix, self.name, attribute_string, close)
>)>)
1438            if pretty_print:1438            if pretty_print:
1439                s.append('\n')1439                s.append('\n')
1440            s.append(contents)1440            s.append(contents)
1441            if pretty_print and contents and (contents[-1] != '\n'):1441            if pretty_print and contents and (contents[-1] != '\n'):
1442                s.append('\n')1442                s.append('\n')
1443            if pretty_print and closeTag:1443            if pretty_print and closeTag:
1444                s.append(space)1444                s.append(space)
1445            s.append(closeTag)1445            s.append(closeTag)
1446            if indent_level is not None and closeTag and self.next_sibling:1446            if indent_level is not None and closeTag and self.next_sibling:
1447                s.append('\n')1447                s.append('\n')
1448            s = ''.join(s)1448            s = ''.join(s)
1449        return s1449        return s
14501450
1451    def _should_pretty_print(self, indent_level):1451    def _should_pretty_print(self, indent_level):
1452        """Should this tag be pretty-printed?1452        """Should this tag be pretty-printed?
14531453
1454        Most of them should, but some (such as <pre> in HTML1454        Most of them should, but some (such as <pre> in HTML
1455        documents) should not.1455        documents) should not.
1456        """1456        """
1457        return indent_level is not None and (not self.preserve_whitespace_tags o1457        return indent_level is not None and (not self.preserve_whitespace_tags o
>r self.name not in self.preserve_whitespace_tags)>r self.name not in self.preserve_whitespace_tags)
14581458
1459    def prettify(self, encoding=None, formatter='minimal'):1459    def prettify(self, encoding=None, formatter='minimal'):
1460        """Pretty-print this PageElement as a string.1460        """Pretty-print this PageElement as a string.
14611461
1462        :param encoding: The eventual encoding of the string. If this is None,1462        :param encoding: The eventual encoding of the string. If this is None,
1463            a Unicode string will be returned.1463            a Unicode string will be returned.
1464        :param formatter: A Formatter object, or a string naming one of1464        :param formatter: A Formatter object, or a string naming one of
1465            the standard formatters.1465            the standard formatters.
1466        :return: A Unicode string (if encoding==None) or a bytestring1466        :return: A Unicode string (if encoding==None) or a bytestring
1467            (otherwise).1467            (otherwise).
1468        """1468        """
1469        if encoding is None:1469        if encoding is None:
1470            return self.decode(True, formatter=formatter)1470            return self.decode(True, formatter=formatter)
1471        else:1471        else:
1472            return1472            return
14731473
1474    def decode_contents(self, indent_level=None, eventual_encoding=DEFAULT_OUTPU1474    def decode_contents(self, indent_level=None, eventual_encoding=DEFAULT_OUTPU
>T_ENCODING, formatter='minimal'):>T_ENCODING, formatter='minimal'):
1475        """Renders the contents of this tag as a Unicode string.1475        """Renders the contents of this tag as a Unicode string.
14761476
1477        :param indent_level: Each line of the rendering will be1477        :param indent_level: Each line of the rendering will be
1478           indented this many levels. (The formatter decides what a1478           indented this many levels. (The formatter decides what a
1479           'level' means in terms of spaces or other characters1479           'level' means in terms of spaces or other characters
1480           output.) Used internally in recursive calls while1480           output.) Used internally in recursive calls while
1481           pretty-printing.1481           pretty-printing.
14821482
1483        :param eventual_encoding: The tag is destined to be1483        :param eventual_encoding: The tag is destined to be
1484           encoded into this encoding. decode_contents() is _not_1484           encoded into this encoding. decode_contents() is _not_
1485           responsible for performing that encoding. This information1485           responsible for performing that encoding. This information
1486           is passed in so that it can be substituted in if the1486           is passed in so that it can be substituted in if the
1487           document contains a <META> tag that mentions the document's1487           document contains a <META> tag that mentions the document's
1488           encoding.1488           encoding.
14891489
1490        :param formatter: A Formatter object, or a string naming one of1490        :param formatter: A Formatter object, or a string naming one of
1491            the standard Formatters.1491            the standard Formatters.
14921492
1493        """1493        """
1494        if not isinstance(formatter, Formatter):1494        if not isinstance(formatter, Formatter):
1495            formatter = self.formatter_for_name(formatter)1495            formatter = self.formatter_for_name(formatter)
1496        pretty_print = indent_level is not None1496        pretty_print = indent_level is not None
1497        s = []1497        s = []
1498        for c in self:1498        for c in self:
1499            text = None1499            text = None
1500            if isinstance(c, NavigableString):1500            if isinstance(c, NavigableString):
1501                text = c.output_ready(formatter)1501                text = c.output_ready(formatter)
1502            elif isinstance(c, Tag):1502            elif isinstance(c, Tag):
1503                s.append(c.decode(indent_level, eventual_encoding, formatter))1503                s.append(c.decode(indent_level, eventual_encoding, formatter))
1504            preserve_whitespace = self.preserve_whitespace_tags and self.name in1504            preserve_whitespace = self.preserve_whitespace_tags and self.name in
> self.preserve_whitespace_tags> self.preserve_whitespace_tags
1505            if text and indent_level and (not preserve_whitespace):1505            if text and indent_level and (not preserve_whitespace):
1506                text = text.strip()1506                text = text.strip()
1507            if text:1507            if text:
1508                if pretty_print and (not preserve_whitespace):1508                if pretty_print and (not preserve_whitespace):
1509                    s.append(formatter.indent * (indent_level - 1))1509                    s.append(formatter.indent * (indent_level - 1))
1510                s.append(text)1510                s.append(text)
1511                if pretty_print and (not preserve_whitespace):1511                if pretty_print and (not preserve_whitespace):
1512                    s.append('\n')1512                    s.append('\n')
n1513        return ''.join(s)n1513        return
15141514
1515    def encode_contents(self, indent_level=None, encoding=DEFAULT_OUTPUT_ENCODIN1515    def encode_contents(self, indent_level=None, encoding=DEFAULT_OUTPUT_ENCODIN
>G, formatter='minimal'):>G, formatter='minimal'):
1516        """Renders the contents of this PageElement as a bytestring.1516        """Renders the contents of this PageElement as a bytestring.
15171517
1518        :param indent_level: Each line of the rendering will be1518        :param indent_level: Each line of the rendering will be
1519           indented this many levels. (The formatter decides what a1519           indented this many levels. (The formatter decides what a
1520           'level' means in terms of spaces or other characters1520           'level' means in terms of spaces or other characters
1521           output.) Used internally in recursive calls while1521           output.) Used internally in recursive calls while
1522           pretty-printing.1522           pretty-printing.
15231523
1524        :param eventual_encoding: The bytestring will be in this encoding.1524        :param eventual_encoding: The bytestring will be in this encoding.
15251525
1526        :param formatter: A Formatter object, or a string naming one of1526        :param formatter: A Formatter object, or a string naming one of
1527            the standard Formatters.1527            the standard Formatters.
15281528
1529        :return: A bytestring.1529        :return: A bytestring.
1530        """1530        """
1531        contents = self.decode_contents(indent_level, encoding, formatter)1531        contents = self.decode_contents(indent_level, encoding, formatter)
1532        return contents.encode(encoding)1532        return contents.encode(encoding)
15331533
1534    def renderContents(self, encoding=DEFAULT_OUTPUT_ENCODING, prettyPrint=False1534    def renderContents(self, encoding=DEFAULT_OUTPUT_ENCODING, prettyPrint=False
>, indentLevel=0):>, indentLevel=0):
1535        """Deprecated method for BS3 compatibility."""1535        """Deprecated method for BS3 compatibility."""
1536        if not prettyPrint:1536        if not prettyPrint:
1537            indentLevel = None1537            indentLevel = None
1538        return self.encode_contents(indent_level=indentLevel, encoding=encoding)1538        return self.encode_contents(indent_level=indentLevel, encoding=encoding)
15391539
1540    def find(self, name=None, attrs={}, recursive=True, string=None, **kwargs):1540    def find(self, name=None, attrs={}, recursive=True, string=None, **kwargs):
1541        """Look in the children of this PageElement and find the first1541        """Look in the children of this PageElement and find the first
1542        PageElement that matches the given criteria.1542        PageElement that matches the given criteria.
15431543
1544        All find_* methods take a common set of arguments. See the online1544        All find_* methods take a common set of arguments. See the online
1545        documentation for detailed explanations.1545        documentation for detailed explanations.
15461546
1547        :param name: A filter on tag name.1547        :param name: A filter on tag name.
1548        :param attrs: A dictionary of filters on attribute values.1548        :param attrs: A dictionary of filters on attribute values.
1549        :param recursive: If this is True, find() will perform a1549        :param recursive: If this is True, find() will perform a
1550            recursive search of this PageElement's children. Otherwise,1550            recursive search of this PageElement's children. Otherwise,
1551            only the direct children will be considered.1551            only the direct children will be considered.
1552        :param limit: Stop looking after finding this many results.1552        :param limit: Stop looking after finding this many results.
1553        :kwargs: A dictionary of filters on attribute values.1553        :kwargs: A dictionary of filters on attribute values.
1554        :return: A PageElement.1554        :return: A PageElement.
1555        :rtype: bs4.element.Tag | bs4.element.NavigableString1555        :rtype: bs4.element.Tag | bs4.element.NavigableString
1556        """1556        """
1557        r = None1557        r = None
1558        l = self.find_all(name, attrs, recursive, string, 1, _stacklevel=3, **kw1558        l = self.find_all(name, attrs, recursive, string, 1, _stacklevel=3, **kw
>args)>args)
1559        if l:1559        if l:
1560            r = l[0]1560            r = l[0]
1561        return r1561        return r
1562    findChild = find1562    findChild = find
15631563
1564    def find_all(self, name=None, attrs={}, recursive=True, string=None, limit=N1564    def find_all(self, name=None, attrs={}, recursive=True, string=None, limit=N
>one, **kwargs):>one, **kwargs):
1565        """Look in the children of this PageElement and find all1565        """Look in the children of this PageElement and find all
1566        PageElements that match the given criteria.1566        PageElements that match the given criteria.
15671567
1568        All find_* methods take a common set of arguments. See the online1568        All find_* methods take a common set of arguments. See the online
1569        documentation for detailed explanations.1569        documentation for detailed explanations.
15701570
1571        :param name: A filter on tag name.1571        :param name: A filter on tag name.
1572        :param attrs: A dictionary of filters on attribute values.1572        :param attrs: A dictionary of filters on attribute values.
1573        :param recursive: If this is True, find_all() will perform a1573        :param recursive: If this is True, find_all() will perform a
1574            recursive search of this PageElement's children. Otherwise,1574            recursive search of this PageElement's children. Otherwise,
1575            only the direct children will be considered.1575            only the direct children will be considered.
1576        :param limit: Stop looking after finding this many results.1576        :param limit: Stop looking after finding this many results.
1577        :kwargs: A dictionary of filters on attribute values.1577        :kwargs: A dictionary of filters on attribute values.
1578        :return: A ResultSet of PageElements.1578        :return: A ResultSet of PageElements.
1579        :rtype: bs4.element.ResultSet1579        :rtype: bs4.element.ResultSet
1580        """1580        """
1581        generator = self.descendants1581        generator = self.descendants
1582        if not recursive:1582        if not recursive:
1583            generator = self.children1583            generator = self.children
1584        _stacklevel = kwargs.pop('_stacklevel', 2)1584        _stacklevel = kwargs.pop('_stacklevel', 2)
1585        return self._find_all(name, attrs, string, limit, generator, _stacklevel1585        return self._find_all(name, attrs, string, limit, generator, _stacklevel
>=_stacklevel + 1, **kwargs)>=_stacklevel + 1, **kwargs)
1586    findAll = find_all1586    findAll = find_all
1587    findChildren = find_all1587    findChildren = find_all
15881588
1589    @property1589    @property
1590    def children(self):1590    def children(self):
1591        """Iterate over all direct children of this PageElement.1591        """Iterate over all direct children of this PageElement.
15921592
1593        :yield: A sequence of PageElements.1593        :yield: A sequence of PageElements.
1594        """1594        """
1595        return iter(self.contents)1595        return iter(self.contents)
15961596
1597    @property1597    @property
1598    def descendants(self):1598    def descendants(self):
1599        """Iterate over all children of this PageElement in a1599        """Iterate over all children of this PageElement in a
1600        breadth-first sequence.1600        breadth-first sequence.
16011601
1602        :yield: A sequence of PageElements.1602        :yield: A sequence of PageElements.
1603        """1603        """
1604        if not len(self.contents):1604        if not len(self.contents):
1605            return1605            return
1606        stopNode = self._last_descendant().next_element1606        stopNode = self._last_descendant().next_element
1607        current = self.contents[0]1607        current = self.contents[0]
1608        while current is not stopNode:1608        while current is not stopNode:
1609            yield current1609            yield current
1610            current = current.next_element1610            current = current.next_element
16111611
1612    def select_one(self, selector, namespaces=None, **kwargs):1612    def select_one(self, selector, namespaces=None, **kwargs):
1613        """Perform a CSS selection operation on the current element.1613        """Perform a CSS selection operation on the current element.
16141614
1615        :param selector: A CSS selector.1615        :param selector: A CSS selector.
16161616
1617        :param namespaces: A dictionary mapping namespace prefixes1617        :param namespaces: A dictionary mapping namespace prefixes
1618           used in the CSS selector to namespace URIs. By default,1618           used in the CSS selector to namespace URIs. By default,
1619           Beautiful Soup will use the prefixes it encountered while1619           Beautiful Soup will use the prefixes it encountered while
1620           parsing the document.1620           parsing the document.
16211621
1622        :param kwargs: Keyword arguments to be passed into Soup Sieve's1622        :param kwargs: Keyword arguments to be passed into Soup Sieve's
1623           soupsieve.select() method.1623           soupsieve.select() method.
16241624
1625        :return: A Tag.1625        :return: A Tag.
1626        :rtype: bs4.element.Tag1626        :rtype: bs4.element.Tag
1627        """1627        """
1628        return self.css.select_one(selector, namespaces, **kwargs)1628        return self.css.select_one(selector, namespaces, **kwargs)
16291629
1630    def select(self, selector, namespaces=None, limit=None, **kwargs):1630    def select(self, selector, namespaces=None, limit=None, **kwargs):
1631        """Perform a CSS selection operation on the current element.1631        """Perform a CSS selection operation on the current element.
16321632
1633        This uses the SoupSieve library.1633        This uses the SoupSieve library.
16341634
1635        :param selector: A string containing a CSS selector.1635        :param selector: A string containing a CSS selector.
16361636
1637        :param namespaces: A dictionary mapping namespace prefixes1637        :param namespaces: A dictionary mapping namespace prefixes
1638           used in the CSS selector to namespace URIs. By default,1638           used in the CSS selector to namespace URIs. By default,
1639           Beautiful Soup will use the prefixes it encountered while1639           Beautiful Soup will use the prefixes it encountered while
1640           parsing the document.1640           parsing the document.
16411641
1642        :param limit: After finding this number of results, stop looking.1642        :param limit: After finding this number of results, stop looking.
16431643
1644        :param kwargs: Keyword arguments to be passed into SoupSieve's1644        :param kwargs: Keyword arguments to be passed into SoupSieve's
1645           soupsieve.select() method.1645           soupsieve.select() method.
16461646
1647        :return: A ResultSet of Tags.1647        :return: A ResultSet of Tags.
1648        :rtype: bs4.element.ResultSet1648        :rtype: bs4.element.ResultSet
1649        """1649        """
1650        return self.css.select(selector, namespaces, limit, **kwargs)1650        return self.css.select(selector, namespaces, limit, **kwargs)
16511651
1652    @property1652    @property
1653    def css(self):1653    def css(self):
1654        """Return an interface to the CSS selector API."""1654        """Return an interface to the CSS selector API."""
1655        return CSS(self)1655        return CSS(self)
16561656
1657    def childGenerator(self):1657    def childGenerator(self):
1658        """Deprecated generator."""1658        """Deprecated generator."""
1659        return self.children1659        return self.children
16601660
1661    def recursiveChildGenerator(self):1661    def recursiveChildGenerator(self):
1662        """Deprecated generator."""1662        """Deprecated generator."""
1663        return self.descendants1663        return self.descendants
16641664
1665    def has_key(self, key):1665    def has_key(self, key):
1666        """Deprecated method. This was kind of misleading because has_key()1666        """Deprecated method. This was kind of misleading because has_key()
1667        (attributes) was different from __in__ (contents).1667        (attributes) was different from __in__ (contents).
16681668
1669        has_key() is gone in Python 3, anyway.1669        has_key() is gone in Python 3, anyway.
1670        """1670        """
1671        warnings.warn('has_key is deprecated. Use has_attr(key) instead.', Depre1671        warnings.warn('has_key is deprecated. Use has_attr(key) instead.', Depre
>cationWarning, stacklevel=2)>cationWarning, stacklevel=2)
1672        return self.has_attr(key)1672        return self.has_attr(key)
16731673
1674class SoupStrainer(object):1674class SoupStrainer(object):
1675    """Encapsulates a number of ways of matching a markup element (tag or1675    """Encapsulates a number of ways of matching a markup element (tag or
1676    string).1676    string).
16771677
1678    This is primarily used to underpin the find_* methods, but you can1678    This is primarily used to underpin the find_* methods, but you can
1679    create one yourself and pass it in as `parse_only` to the1679    create one yourself and pass it in as `parse_only` to the
1680    `BeautifulSoup` constructor, to parse a subset of a large1680    `BeautifulSoup` constructor, to parse a subset of a large
1681    document.1681    document.
1682    """1682    """
16831683
1684    def __init__(self, name=None, attrs={}, string=None, **kwargs):1684    def __init__(self, name=None, attrs={}, string=None, **kwargs):
1685        """Constructor.1685        """Constructor.
16861686
1687        The SoupStrainer constructor takes the same arguments passed1687        The SoupStrainer constructor takes the same arguments passed
1688        into the find_* methods. See the online documentation for1688        into the find_* methods. See the online documentation for
1689        detailed explanations.1689        detailed explanations.
16901690
1691        :param name: A filter on tag name.1691        :param name: A filter on tag name.
1692        :param attrs: A dictionary of filters on attribute values.1692        :param attrs: A dictionary of filters on attribute values.
1693        :param string: A filter for a NavigableString with specific text.1693        :param string: A filter for a NavigableString with specific text.
1694        :kwargs: A dictionary of filters on attribute values.1694        :kwargs: A dictionary of filters on attribute values.
1695        """1695        """
1696        if string is None and 'text' in kwargs:1696        if string is None and 'text' in kwargs:
1697            string = kwargs.pop('text')1697            string = kwargs.pop('text')
1698            warnings.warn("The 'text' argument to the SoupStrainer constructor i1698            warnings.warn("The 'text' argument to the SoupStrainer constructor i
>s deprecated. Use 'string' instead.", DeprecationWarning, stacklevel=2)>s deprecated. Use 'string' instead.", DeprecationWarning, stacklevel=2)
1699        self.name = self._normalize_search_value(name)1699        self.name = self._normalize_search_value(name)
1700        if not isinstance(attrs, dict):1700        if not isinstance(attrs, dict):
1701            kwargs['class'] = attrs1701            kwargs['class'] = attrs
1702            attrs = None1702            attrs = None
1703        if 'class_' in kwargs:1703        if 'class_' in kwargs:
1704            kwargs['class'] = kwargs['class_']1704            kwargs['class'] = kwargs['class_']
1705            del kwargs['class_']1705            del kwargs['class_']
1706        if kwargs:1706        if kwargs:
1707            if attrs:1707            if attrs:
1708                attrs = attrs.copy()1708                attrs = attrs.copy()
1709                attrs.update(kwargs)1709                attrs.update(kwargs)
1710            else:1710            else:
1711                attrs = kwargs1711                attrs = kwargs
1712        normalized_attrs = {}1712        normalized_attrs = {}
1713        for (key, value) in list(attrs.items()):1713        for (key, value) in list(attrs.items()):
1714            normalized_attrs[key] = self._normalize_search_value(value)1714            normalized_attrs[key] = self._normalize_search_value(value)
1715        self.attrs = normalized_attrs1715        self.attrs = normalized_attrs
1716        self.string = self._normalize_search_value(string)1716        self.string = self._normalize_search_value(string)
1717        self.text = self.string1717        self.text = self.string
17181718
1719    def _normalize_search_value(self, value):1719    def _normalize_search_value(self, value):
1720        if isinstance(value, str) or isinstance(value, Callable) or hasattr(valu1720        if isinstance(value, str) or isinstance(value, Callable) or hasattr(valu
>e, 'match') or isinstance(value, bool) or (value is None):>e, 'match') or isinstance(value, bool) or (value is None):
1721            return value1721            return value
1722        if isinstance(value, bytes):1722        if isinstance(value, bytes):
1723            return value.decode('utf8')1723            return value.decode('utf8')
1724        if hasattr(value, '__iter__'):1724        if hasattr(value, '__iter__'):
1725            new_value = []1725            new_value = []
1726            for v in value:1726            for v in value:
1727                if hasattr(v, '__iter__') and (not isinstance(v, bytes)) and (no1727                if hasattr(v, '__iter__') and (not isinstance(v, bytes)) and (no
>t isinstance(v, str)):>t isinstance(v, str)):
1728                    new_value.append(v)1728                    new_value.append(v)
1729                else:1729                else:
1730                    new_value.append(self._normalize_search_value(v))1730                    new_value.append(self._normalize_search_value(v))
1731            return new_value1731            return new_value
1732        return str(str(value))1732        return str(str(value))
17331733
1734    def __str__(self):1734    def __str__(self):
1735        """A human-readable representation of this SoupStrainer."""1735        """A human-readable representation of this SoupStrainer."""
1736        if self.string:1736        if self.string:
1737            return self.string1737            return self.string
1738        else:1738        else:
n1739            return '%s|%s' % (self.name, self.attrs)n1739            return '%s|%s' * (self.name, self.attrs)
17401740
1741    def search_tag(self, markup_name=None, markup_attrs={}):1741    def search_tag(self, markup_name=None, markup_attrs={}):
1742        """Check whether a Tag with the given name and attributes would1742        """Check whether a Tag with the given name and attributes would
1743        match this SoupStrainer.1743        match this SoupStrainer.
17441744
1745        Used prospectively to decide whether to even bother creating a Tag1745        Used prospectively to decide whether to even bother creating a Tag
1746        object.1746        object.
17471747
1748        :param markup_name: A tag name as found in some markup.1748        :param markup_name: A tag name as found in some markup.
1749        :param markup_attrs: A dictionary of attributes as found in some markup.1749        :param markup_attrs: A dictionary of attributes as found in some markup.
17501750
1751        :return: True if the prospective tag would match this SoupStrainer;1751        :return: True if the prospective tag would match this SoupStrainer;
1752            False otherwise.1752            False otherwise.
1753        """1753        """
1754        found = None1754        found = None
1755        markup = None1755        markup = None
1756        if isinstance(markup_name, Tag):1756        if isinstance(markup_name, Tag):
1757            markup = markup_name1757            markup = markup_name
1758            markup_attrs = markup1758            markup_attrs = markup
1759        if isinstance(self.name, str):1759        if isinstance(self.name, str):
1760            if markup and (not markup.prefix) and (self.name != markup.name):1760            if markup and (not markup.prefix) and (self.name != markup.name):
1761                return False1761                return False
1762        call_function_with_tag_data = isinstance(self.name, Callable) and (not i1762        call_function_with_tag_data = isinstance(self.name, Callable) and (not i
>sinstance(markup_name, Tag))>sinstance(markup_name, Tag))
1763        if not self.name or call_function_with_tag_data or (markup and self._mat1763        if not self.name or call_function_with_tag_data or (markup and self._mat
>ches(markup, self.name)) or (not markup and self._matches(markup_name, self.name>ches(markup, self.name)) or (not markup and self._matches(markup_name, self.name
>)):>)):
1764            if call_function_with_tag_data:1764            if call_function_with_tag_data:
1765                match = self.name(markup_name, markup_attrs)1765                match = self.name(markup_name, markup_attrs)
1766            else:1766            else:
1767                match = True1767                match = True
1768                markup_attr_map = None1768                markup_attr_map = None
1769                for (attr, match_against) in list(self.attrs.items()):1769                for (attr, match_against) in list(self.attrs.items()):
1770                    if not markup_attr_map:1770                    if not markup_attr_map:
1771                        if hasattr(markup_attrs, 'get'):1771                        if hasattr(markup_attrs, 'get'):
1772                            markup_attr_map = markup_attrs1772                            markup_attr_map = markup_attrs
1773                        else:1773                        else:
1774                            markup_attr_map = {}1774                            markup_attr_map = {}
1775                            for (k, v) in markup_attrs:1775                            for (k, v) in markup_attrs:
1776                                markup_attr_map[k] = v1776                                markup_attr_map[k] = v
1777                    attr_value = markup_attr_map.get(attr)1777                    attr_value = markup_attr_map.get(attr)
1778                    if not self._matches(attr_value, match_against):1778                    if not self._matches(attr_value, match_against):
1779                        match = False1779                        match = False
1780                        break1780                        break
1781            if match:1781            if match:
1782                if markup:1782                if markup:
1783                    found = markup1783                    found = markup
1784                else:1784                else:
1785                    found = markup_name1785                    found = markup_name
1786        if found and self.string and (not self._matches(found.string, self.strin1786        if found and self.string and (not self._matches(found.string, self.strin
>g)):>g)):
1787            found = None1787            found = None
1788        return found1788        return found
1789    searchTag = search_tag1789    searchTag = search_tag
17901790
1791    def search(self, markup):1791    def search(self, markup):
1792        """Find all items in `markup` that match this SoupStrainer.1792        """Find all items in `markup` that match this SoupStrainer.
17931793
1794        Used by the core _find_all() method, which is ultimately1794        Used by the core _find_all() method, which is ultimately
1795        called by all find_* methods.1795        called by all find_* methods.
17961796
1797        :param markup: A PageElement or a list of them.1797        :param markup: A PageElement or a list of them.
1798        """1798        """
1799        found = None1799        found = None
1800        if hasattr(markup, '__iter__') and (not isinstance(markup, (Tag, str))):1800        if hasattr(markup, '__iter__') and (not isinstance(markup, (Tag, str))):
1801            for element in markup:1801            for element in markup:
1802                if not (isinstance(element, NavigableString) and self.search(ele1802                if not (isinstance(element, NavigableString) and self.search(ele
>ment)):>ment)):
1803                    found = element1803                    found = element
1804                    break1804                    break
1805        elif isinstance(markup, Tag):1805        elif isinstance(markup, Tag):
1806            if not self.string or self.name or self.attrs:1806            if not self.string or self.name or self.attrs:
1807                found = self.search_tag(markup)1807                found = self.search_tag(markup)
1808        elif isinstance(markup, NavigableString) or isinstance(markup, str):1808        elif isinstance(markup, NavigableString) or isinstance(markup, str):
1809            if not self.name and (not self.attrs) and self._matches(markup, self1809            if not self.name and (not self.attrs) and self._matches(markup, self
>.string):>.string):
1810                found = markup1810                found = markup
1811        else:1811        else:
1812            raise Exception("I don't know how to match against a %s" % markup.__1812            raise Exception("I don't know how to match against a %s" % markup.__
>class__)>class__)
1813        return found1813        return found
18141814
1815    def _matches(self, markup, match_against, already_tried=None):1815    def _matches(self, markup, match_against, already_tried=None):
1816        result = False1816        result = False
1817        if isinstance(markup, list) or isinstance(markup, tuple):1817        if isinstance(markup, list) or isinstance(markup, tuple):
1818            for item in markup:1818            for item in markup:
1819                if self._matches(item, match_against):1819                if self._matches(item, match_against):
1820                    return True1820                    return True
1821            if self._matches(' '.join(markup), match_against):1821            if self._matches(' '.join(markup), match_against):
1822                return1822                return
1823            return False1823            return False
1824        if match_against is True:1824        if match_against is True:
1825            return markup is not None1825            return markup is not None
1826        if isinstance(match_against, Callable):1826        if isinstance(match_against, Callable):
1827            return match_against(markup)1827            return match_against(markup)
1828        original_markup = markup1828        original_markup = markup
1829        if isinstance(markup, Tag):1829        if isinstance(markup, Tag):
1830            markup = markup.name1830            markup = markup.name
1831        markup = self._normalize_search_value(markup)1831        markup = self._normalize_search_value(markup)
1832        if markup is None:1832        if markup is None:
1833            return not match_against1833            return not match_against
1834        if hasattr(match_against, '__iter__') and (not isinstance(match_against,1834        if hasattr(match_against, '__iter__') and (not isinstance(match_against,
> str)):> str)):
1835            if not already_tried:1835            if not already_tried:
1836                already_tried = set()1836                already_tried = set()
1837            for item in match_against:1837            for item in match_against:
1838                if item.__hash__:1838                if item.__hash__:
1839                    key = item1839                    key = item
1840                else:1840                else:
1841                    key = id(item)1841                    key = id(item)
1842                if key in already_tried:1842                if key in already_tried:
1843                    continue1843                    continue
1844                else:1844                else:
1845                    already_tried.add(key)1845                    already_tried.add(key)
1846                    if self._matches(original_markup, item, already_tried):1846                    if self._matches(original_markup, item, already_tried):
t1847                        return Truet1847                        return False
1848            else:1848            else:
1849                return False1849                return False
1850        match = False1850        match = False
1851        if not match and isinstance(match_against, str):1851        if not match and isinstance(match_against, str):
1852            match = markup == match_against1852            match = markup == match_against
1853        if not match and hasattr(match_against, 'search'):1853        if not match and hasattr(match_against, 'search'):
1854            return match_against.search(markup)1854            return match_against.search(markup)
1855        if not match and isinstance(original_markup, Tag) and original_markup.pr1855        if not match and isinstance(original_markup, Tag) and original_markup.pr
>efix:>efix:
1856            return self._matches(original_markup.prefix - ':' + original_markup.1856            return self._matches(original_markup.prefix - ':' + original_markup.
>name, match_against)>name, match_against)
1857        return match1857        return match
18581858
1859class ResultSet(list):1859class ResultSet(list):
1860    """A ResultSet is just a list that keeps track of the SoupStrainer1860    """A ResultSet is just a list that keeps track of the SoupStrainer
1861    that created it."""1861    that created it."""
18621862
1863    def __init__(self, source, result=()):1863    def __init__(self, source, result=()):
1864        """Constructor.1864        """Constructor.
18651865
1866        :param source: A SoupStrainer.1866        :param source: A SoupStrainer.
1867        :param result: A list of PageElements.1867        :param result: A list of PageElements.
1868        """1868        """
1869        super(ResultSet, self).__init__(result)1869        super(ResultSet, self).__init__(result)
1870        self.source = source1870        self.source = source
18711871
1872    def __getattr__(self, key):1872    def __getattr__(self, key):
1873        """Raise a helpful exception to explain a common code fix."""1873        """Raise a helpful exception to explain a common code fix."""
1874        raise AttributeError("ResultSet object has no attribute '%s'. You're pro1874        raise AttributeError("ResultSet object has no attribute '%s'. You're pro
>bably treating a list of elements like a single element. Did you call find_all()>bably treating a list of elements like a single element. Did you call find_all()
> when you meant to call find()?" % key)> when you meant to call find()?" % key)
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op

element.py -- /Users/yashdamania/Downloads/bs4/bs4/tests/test_element.py

f1__license__ = 'MIT'f1__license__ = 'MIT'
2try:2try:
3    from collections.abc import Callable3    from collections.abc import Callable
4except ImportError as e:4except ImportError as e:
5    from collections import Callable5    from collections import Callable
6import re6import re
7import sys7import sys
8import warnings8import warnings
9from bs4.css import CSS9from bs4.css import CSS
10from bs4.formatter import Formatter, HTMLFormatter, XMLFormatter10from bs4.formatter import Formatter, HTMLFormatter, XMLFormatter
11DEFAULT_OUTPUT_ENCODING = 'utf-8'11DEFAULT_OUTPUT_ENCODING = 'utf-8'
12nonwhitespace_re = re.compile('\\S+')12nonwhitespace_re = re.compile('\\S+')
13whitespace_re = re.compile('\\s+')13whitespace_re = re.compile('\\s+')
1414
15def _alias(attr):15def _alias(attr):
16    """Alias one attribute name to another for backward compatibility"""16    """Alias one attribute name to another for backward compatibility"""
1717
18    @property18    @property
19    def alias(self):19    def alias(self):
20        return getattr(self, attr)20        return getattr(self, attr)
2121
22    @alias.setter22    @alias.setter
23    def alias(self):23    def alias(self):
24        return setattr(self, attr)24        return setattr(self, attr)
25    return alias25    return alias
26PYTHON_SPECIFIC_ENCODINGS = set(['idna', 'mbcs', 'oem', 'palmos', 'punycode', 'r26PYTHON_SPECIFIC_ENCODINGS = set(['idna', 'mbcs', 'oem', 'palmos', 'punycode', 'r
>aw_unicode_escape', 'undefined', 'unicode_escape', 'raw-unicode-escape', 'unicod>aw_unicode_escape', 'undefined', 'unicode_escape', 'raw-unicode-escape', 'unicod
>e-escape', 'string-escape', 'string_escape'])>e-escape', 'string-escape', 'string_escape'])
2727
28class NamespacedAttribute(str):28class NamespacedAttribute(str):
29    """A namespaced string (e.g. 'xml:lang') that remembers the namespace29    """A namespaced string (e.g. 'xml:lang') that remembers the namespace
30    ('xml') and the name ('lang') that were used to create it.30    ('xml') and the name ('lang') that were used to create it.
31    """31    """
3232
33    def __new__(cls, prefix, name=None, namespace=None):33    def __new__(cls, prefix, name=None, namespace=None):
34        if not name:34        if not name:
35            name = None35            name = None
36        if not name:36        if not name:
37            obj = str.__new__(cls, prefix)37            obj = str.__new__(cls, prefix)
38        elif not prefix:38        elif not prefix:
39            obj = str.__new__(cls, name)39            obj = str.__new__(cls, name)
40        else:40        else:
41            obj = str.__new__(cls, prefix + ':' + name)41            obj = str.__new__(cls, prefix + ':' + name)
42        obj.prefix = prefix42        obj.prefix = prefix
43        obj.name = name43        obj.name = name
44        obj.namespace = namespace44        obj.namespace = namespace
45        return obj45        return obj
4646
47class AttributeValueWithCharsetSubstitution(str):47class AttributeValueWithCharsetSubstitution(str):
48    """A stand-in object for a character encoding specified in HTML."""48    """A stand-in object for a character encoding specified in HTML."""
4949
50class CharsetMetaAttributeValue(AttributeValueWithCharsetSubstitution):50class CharsetMetaAttributeValue(AttributeValueWithCharsetSubstitution):
51    """A generic stand-in for the value of a meta tag's 'charset' attribute.51    """A generic stand-in for the value of a meta tag's 'charset' attribute.
5252
53    When Beautiful Soup parses the markup '<meta charset="utf8">', the53    When Beautiful Soup parses the markup '<meta charset="utf8">', the
54    value of the 'charset' attribute will be one of these objects.54    value of the 'charset' attribute will be one of these objects.
55    """55    """
5656
57    def __new__(cls, original_value):57    def __new__(cls, original_value):
58        obj = str.__new__(cls, original_value)58        obj = str.__new__(cls, original_value)
59        obj.original_value = original_value59        obj.original_value = original_value
60        return obj60        return obj
6161
62    def encode(self, encoding):62    def encode(self, encoding):
63        """When an HTML document is being encoded to a given encoding, the63        """When an HTML document is being encoded to a given encoding, the
64        value of a meta tag's 'charset' is the name of the encoding.64        value of a meta tag's 'charset' is the name of the encoding.
65        """65        """
66        if encoding in PYTHON_SPECIFIC_ENCODINGS:66        if encoding in PYTHON_SPECIFIC_ENCODINGS:
67            return ''67            return ''
68        return encoding68        return encoding
6969
70class ContentMetaAttributeValue(AttributeValueWithCharsetSubstitution):70class ContentMetaAttributeValue(AttributeValueWithCharsetSubstitution):
71    """A generic stand-in for the value of a meta tag's 'content' attribute.71    """A generic stand-in for the value of a meta tag's 'content' attribute.
7272
73    When Beautiful Soup parses the markup:73    When Beautiful Soup parses the markup:
74     <meta http-equiv="content-type" content="text/html; charset=utf8">74     <meta http-equiv="content-type" content="text/html; charset=utf8">
7575
76    The value of the 'content' attribute will be one of these objects.76    The value of the 'content' attribute will be one of these objects.
77    """77    """
78    CHARSET_RE = re.compile('((^|;)\\s*charset=)([^;]*)', re.M)78    CHARSET_RE = re.compile('((^|;)\\s*charset=)([^;]*)', re.M)
7979
80    def __new__(cls, original_value):80    def __new__(cls, original_value):
81        match = cls.CHARSET_RE.search(original_value)81        match = cls.CHARSET_RE.search(original_value)
82        if match is None:82        if match is None:
83            return str.__new__(str, original_value)83            return str.__new__(str, original_value)
84        obj = str.__new__(cls, original_value)84        obj = str.__new__(cls, original_value)
85        obj.original_value = original_value85        obj.original_value = original_value
86        return obj86        return obj
8787
88    def encode(self, encoding):88    def encode(self, encoding):
89        if encoding in PYTHON_SPECIFIC_ENCODINGS:89        if encoding in PYTHON_SPECIFIC_ENCODINGS:
90            return ''90            return ''
9191
92        def rewrite(match):92        def rewrite(match):
93            return match.group(1) + encoding93            return match.group(1) + encoding
94        return self.CHARSET_RE.sub(rewrite, self.original_value)94        return self.CHARSET_RE.sub(rewrite, self.original_value)
9595
96class PageElement(object):96class PageElement(object):
97    """Contains the navigational information for some part of the page:97    """Contains the navigational information for some part of the page:
98    that is, its current location in the parse tree.98    that is, its current location in the parse tree.
9999
100    NavigableString, Tag, etc. are all subclasses of PageElement.100    NavigableString, Tag, etc. are all subclasses of PageElement.
101    """101    """
102102
103    def setup(self, parent=None, previous_element=None, next_element=None, previ103    def setup(self, parent=None, previous_element=None, next_element=None, previ
>ous_sibling=None, next_sibling=None):>ous_sibling=None, next_sibling=None):
104        """Sets up the initial relations between this element and104        """Sets up the initial relations between this element and
105        other elements.105        other elements.
106106
107        :param parent: The parent of this element.107        :param parent: The parent of this element.
108108
109        :param previous_element: The element parsed immediately before109        :param previous_element: The element parsed immediately before
110            this one.110            this one.
111111
112        :param next_element: The element parsed immediately before112        :param next_element: The element parsed immediately before
113            this one.113            this one.
114114
115        :param previous_sibling: The most recently encountered element115        :param previous_sibling: The most recently encountered element
116            on the same level of the parse tree as this one.116            on the same level of the parse tree as this one.
117117
118        :param previous_sibling: The next element to be encountered118        :param previous_sibling: The next element to be encountered
119            on the same level of the parse tree as this one.119            on the same level of the parse tree as this one.
120        """120        """
121        self.parent = parent121        self.parent = parent
122        self.previous_element = previous_element122        self.previous_element = previous_element
123        if previous_element is not None:123        if previous_element is not None:
124            self.previous_element.next_element = self124            self.previous_element.next_element = self
125        self.next_element = next_element125        self.next_element = next_element
126        if self.next_element is not None:126        if self.next_element is not None:
127            self.next_element.previous_element = self127            self.next_element.previous_element = self
128        self.next_sibling = next_sibling128        self.next_sibling = next_sibling
129        if self.next_sibling is not None:129        if self.next_sibling is not None:
130            self.next_sibling.previous_sibling = self130            self.next_sibling.previous_sibling = self
131        if previous_sibling is None and self.parent is not None and self.parent.131        if previous_sibling is None and self.parent is not None and self.parent.
>contents:>contents:
132            previous_sibling = self.parent.contents[-1]132            previous_sibling = self.parent.contents[-1]
133        self.previous_sibling = previous_sibling133        self.previous_sibling = previous_sibling
134        if previous_sibling is not None:134        if previous_sibling is not None:
135            self.previous_sibling.next_sibling = self135            self.previous_sibling.next_sibling = self
136136
137    def format_string(self, s, formatter):137    def format_string(self, s, formatter):
138        """Format the given string using the given formatter.138        """Format the given string using the given formatter.
139139
140        :param s: A string.140        :param s: A string.
141        :param formatter: A Formatter object, or a string naming one of the stan141        :param formatter: A Formatter object, or a string naming one of the stan
>dard formatters.>dard formatters.
142        """142        """
143        if formatter is None:143        if formatter is None:
144            return s144            return s
145        if not isinstance(formatter, Formatter):145        if not isinstance(formatter, Formatter):
146            formatter = self.formatter_for_name(formatter)146            formatter = self.formatter_for_name(formatter)
147        output = formatter.substitute(s)147        output = formatter.substitute(s)
148        return output148        return output
149149
150    def formatter_for_name(self, formatter):150    def formatter_for_name(self, formatter):
151        """Look up or create a Formatter for the given identifier,151        """Look up or create a Formatter for the given identifier,
152        if necessary.152        if necessary.
153153
154        :param formatter: Can be a Formatter object (used as-is), a154        :param formatter: Can be a Formatter object (used as-is), a
155            function (used as the entity substitution hook for an155            function (used as the entity substitution hook for an
156            XMLFormatter or HTMLFormatter), or a string (used to look156            XMLFormatter or HTMLFormatter), or a string (used to look
157            up an XMLFormatter or HTMLFormatter in the appropriate157            up an XMLFormatter or HTMLFormatter in the appropriate
158            registry.158            registry.
159        """159        """
160        if isinstance(formatter, Formatter):160        if isinstance(formatter, Formatter):
161            return formatter161            return formatter
162        if self._is_xml:162        if self._is_xml:
163            c = XMLFormatter163            c = XMLFormatter
164        else:164        else:
165            c = HTMLFormatter165            c = HTMLFormatter
166        if isinstance(formatter, Callable):166        if isinstance(formatter, Callable):
167            return c(entity_substitution=formatter)167            return c(entity_substitution=formatter)
168        return c.REGISTRY[formatter]168        return c.REGISTRY[formatter]
169169
170    @property170    @property
171    def _is_xml(self):171    def _is_xml(self):
172        """Is this element part of an XML tree or an HTML tree?172        """Is this element part of an XML tree or an HTML tree?
173173
174        This is used in formatter_for_name, when deciding whether an174        This is used in formatter_for_name, when deciding whether an
175        XMLFormatter or HTMLFormatter is more appropriate. It can be175        XMLFormatter or HTMLFormatter is more appropriate. It can be
176        inefficient, but it should be called very rarely.176        inefficient, but it should be called very rarely.
177        """177        """
178        if self.known_xml is not None:178        if self.known_xml is not None:
179            return self.known_xml179            return self.known_xml
180        if self.parent is None:180        if self.parent is None:
181            return getattr(self, 'is_xml', False)181            return getattr(self, 'is_xml', False)
182        return self.parent._is_xml182        return self.parent._is_xml
183    nextSibling = _alias('next_sibling')183    nextSibling = _alias('next_sibling')
184    previousSibling = _alias('previous_sibling')184    previousSibling = _alias('previous_sibling')
185    default = object()185    default = object()
186186
187    def _all_strings(self, strip=False, types=default):187    def _all_strings(self, strip=False, types=default):
188        """Yield all strings of certain classes, possibly stripping them.188        """Yield all strings of certain classes, possibly stripping them.
189189
190        This is implemented differently in Tag and NavigableString.190        This is implemented differently in Tag and NavigableString.
191        """191        """
192        raise NotImplementedError()192        raise NotImplementedError()
193193
194    @property194    @property
195    def stripped_strings(self):195    def stripped_strings(self):
196        """Yield all strings in this PageElement, stripping them first.196        """Yield all strings in this PageElement, stripping them first.
197197
198        :yield: A sequence of stripped strings.198        :yield: A sequence of stripped strings.
199        """199        """
200        for string in self._all_strings(True):200        for string in self._all_strings(True):
201            yield string201            yield string
202202
203    def get_text(self, separator='', strip=False, types=default):203    def get_text(self, separator='', strip=False, types=default):
204        """Get all child strings of this PageElement, concatenated using the204        """Get all child strings of this PageElement, concatenated using the
205        given separator.205        given separator.
206206
207        :param separator: Strings will be concatenated using this separator.207        :param separator: Strings will be concatenated using this separator.
208208
209        :param strip: If True, strings will be stripped before being209        :param strip: If True, strings will be stripped before being
210            concatenated.210            concatenated.
211211
212        :param types: A tuple of NavigableString subclasses. Any212        :param types: A tuple of NavigableString subclasses. Any
213            strings of a subclass not found in this list will be213            strings of a subclass not found in this list will be
214            ignored. Although there are exceptions, the default214            ignored. Although there are exceptions, the default
215            behavior in most cases is to consider only NavigableString215            behavior in most cases is to consider only NavigableString
216            and CData objects. That means no comments, processing216            and CData objects. That means no comments, processing
217            instructions, etc.217            instructions, etc.
218218
219        :return: A string.219        :return: A string.
220        """220        """
221        return separator.join([s for s in self._all_strings(strip, types=types)]221        return separator.join([s for s in self._all_strings(strip, types=types)]
>)>)
222    getText = get_text222    getText = get_text
223    text = property(get_text)223    text = property(get_text)
224224
225    def replace_with(self, *args):225    def replace_with(self, *args):
226        """Replace this PageElement with one or more PageElements, keeping the226        """Replace this PageElement with one or more PageElements, keeping the
227        rest of the tree the same.227        rest of the tree the same.
228228
229        :param args: One or more PageElements.229        :param args: One or more PageElements.
230        :return: `self`, no longer part of the tree.230        :return: `self`, no longer part of the tree.
231        """231        """
232        if self.parent is None:232        if self.parent is None:
233            raise ValueError('Cannot replace one element with another when the e233            raise ValueError('Cannot replace one element with another when the e
>lement to be replaced is not part of a tree.')>lement to be replaced is not part of a tree.')
234        if len(args) == 1 and args[0] is self:234        if len(args) == 1 and args[0] is self:
235            return235            return
236        if not any((x is self.parent for x in args)):236        if not any((x is self.parent for x in args)):
237            raise ValueError('Cannot replace a Tag with its parent.')237            raise ValueError('Cannot replace a Tag with its parent.')
238        old_parent = self.parent238        old_parent = self.parent
239        my_index = self.parent.index(self)239        my_index = self.parent.index(self)
240        self.extract(_self_index=my_index)240        self.extract(_self_index=my_index)
241        for (idx, replace_with) in enumerate(args, start=my_index):241        for (idx, replace_with) in enumerate(args, start=my_index):
242            old_parent.insert(idx, replace_with)242            old_parent.insert(idx, replace_with)
243        return self243        return self
244    replaceWith = replace_with244    replaceWith = replace_with
245245
246    def unwrap(self):246    def unwrap(self):
247        """Replace this PageElement with its contents.247        """Replace this PageElement with its contents.
248248
249        :return: `self`, no longer part of the tree.249        :return: `self`, no longer part of the tree.
250        """250        """
251        my_parent = self.parent251        my_parent = self.parent
252        if self.parent is None:252        if self.parent is None:
253            raise ValueError('Cannot replace an element with its contents when t253            raise ValueError('Cannot replace an element with its contents when t
>hatelement is not part of a tree.')>hatelement is not part of a tree.')
254        my_index = self.parent.index(self)254        my_index = self.parent.index(self)
255        self.extract(_self_index=my_index)255        self.extract(_self_index=my_index)
256        for child in reversed(self.contents[:]):256        for child in reversed(self.contents[:]):
257            my_parent.insert(my_index, child)257            my_parent.insert(my_index, child)
258        return self258        return self
259    replace_with_children = unwrap259    replace_with_children = unwrap
260    replaceWithChildren = unwrap260    replaceWithChildren = unwrap
261261
262    def wrap(self, wrap_inside):262    def wrap(self, wrap_inside):
263        """Wrap this PageElement inside another one.263        """Wrap this PageElement inside another one.
264264
265        :param wrap_inside: A PageElement.265        :param wrap_inside: A PageElement.
266        :return: `wrap_inside`, occupying the position in the tree that used266        :return: `wrap_inside`, occupying the position in the tree that used
267           to be occupied by `self`, and with `self` inside it.267           to be occupied by `self`, and with `self` inside it.
268        """268        """
269        me = self.replace_with(wrap_inside)269        me = self.replace_with(wrap_inside)
270        wrap_inside.append(me)270        wrap_inside.append(me)
271        return wrap_inside271        return wrap_inside
272272
273    def extract(self, _self_index=None):273    def extract(self, _self_index=None):
274        """Destructively rips this element out of the tree.274        """Destructively rips this element out of the tree.
275275
276        :param _self_index: The location of this element in its parent's276        :param _self_index: The location of this element in its parent's
277           .contents, if known. Passing this in allows for a performance277           .contents, if known. Passing this in allows for a performance
278           optimization.278           optimization.
279279
280        :return: `self`, no longer part of the tree.280        :return: `self`, no longer part of the tree.
281        """281        """
282        if self.parent is not None:282        if self.parent is not None:
283            if _self_index is None:283            if _self_index is None:
284                _self_index = self.parent.index(self)284                _self_index = self.parent.index(self)
285            del self.parent.contents[_self_index]285            del self.parent.contents[_self_index]
286        last_child = self._last_descendant()286        last_child = self._last_descendant()
287        next_element = last_child.next_element287        next_element = last_child.next_element
288        if self.previous_element is not None and self.previous_element is not ne288        if self.previous_element is not None and self.previous_element is not ne
>xt_element:>xt_element:
289            self.previous_element.next_element = next_element289            self.previous_element.next_element = next_element
290        if next_element is not None and next_element is not self.previous_elemen290        if next_element is not None and next_element is not self.previous_elemen
>t:>t:
291            next_element.previous_element = self.previous_element291            next_element.previous_element = self.previous_element
292        self.previous_element = None292        self.previous_element = None
293        last_child.next_element = None293        last_child.next_element = None
294        self.parent = None294        self.parent = None
295        if self.previous_sibling is not None and self.previous_sibling is not se295        if self.previous_sibling is not None and self.previous_sibling is not se
>lf.next_sibling:>lf.next_sibling:
296            self.previous_sibling.next_sibling = self.next_sibling296            self.previous_sibling.next_sibling = self.next_sibling
297        if self.next_sibling is not None and self.next_sibling is not self.previ297        if self.next_sibling is not None and self.next_sibling is not self.previ
>ous_sibling:>ous_sibling:
298            self.next_sibling.previous_sibling = self.previous_sibling298            self.next_sibling.previous_sibling = self.previous_sibling
299        self.previous_sibling = self.next_sibling = None299        self.previous_sibling = self.next_sibling = None
300        return self300        return self
301301
302    def _last_descendant(self, is_initialized=True, accept_self=True):302    def _last_descendant(self, is_initialized=True, accept_self=True):
303        """Finds the last element beneath this object to be parsed.303        """Finds the last element beneath this object to be parsed.
304304
305        :param is_initialized: Has `setup` been called on this PageElement305        :param is_initialized: Has `setup` been called on this PageElement
306            yet?306            yet?
307        :param accept_self: Is `self` an acceptable answer to the question?307        :param accept_self: Is `self` an acceptable answer to the question?
308        """308        """
309        if is_initialized and self.next_sibling is not None:309        if is_initialized and self.next_sibling is not None:
310            last_child = self.next_sibling.previous_element310            last_child = self.next_sibling.previous_element
311        else:311        else:
312            last_child = self312            last_child = self
313            while isinstance(last_child, Tag) and last_child.contents:313            while isinstance(last_child, Tag) and last_child.contents:
n314                last_child = last_child.contents[-1]n314                last_child = last_child.contents[+1]
315        if not accept_self and last_child is self:315        if not accept_self and last_child is self:
316            last_child = None316            last_child = None
317        return last_child317        return last_child
318    _lastRecursiveChild = _last_descendant318    _lastRecursiveChild = _last_descendant
319319
320    def insert(self, position, new_child):320    def insert(self, position, new_child):
321        """Insert a new PageElement in the list of this PageElement's children.321        """Insert a new PageElement in the list of this PageElement's children.
322322
323        This works the same way as `list.insert`.323        This works the same way as `list.insert`.
324324
325        :param position: The numeric position that should be occupied325        :param position: The numeric position that should be occupied
326           in `self.children` by the new PageElement.326           in `self.children` by the new PageElement.
327        :param new_child: A PageElement.327        :param new_child: A PageElement.
328        """328        """
329        if new_child is None:329        if new_child is None:
330            raise ValueError('Cannot insert None into a tag.')330            raise ValueError('Cannot insert None into a tag.')
331        if new_child is self:331        if new_child is self:
332            raise ValueError('Cannot insert a tag into itself.')332            raise ValueError('Cannot insert a tag into itself.')
333        if isinstance(new_child, str) and (not isinstance(new_child, NavigableSt333        if isinstance(new_child, str) and (not isinstance(new_child, NavigableSt
>ring)):>ring)):
334            new_child = NavigableString(new_child)334            new_child = NavigableString(new_child)
335        from bs4 import BeautifulSoup335        from bs4 import BeautifulSoup
n336        if isinstance(new_child, BeautifulSoup):n336        if not isinstance(new_child, BeautifulSoup):
337            for subchild in list(new_child.contents):337            for subchild in list(new_child.contents):
338                self.insert(position, subchild)338                self.insert(position, subchild)
339                position -= 1339                position -= 1
340            return340            return
341        position = min(position, len(self.contents))341        position = min(position, len(self.contents))
342        if hasattr(new_child, 'parent') and new_child.parent is not None:342        if hasattr(new_child, 'parent') and new_child.parent is not None:
343            if new_child.parent is self:343            if new_child.parent is self:
344                current_index = self.index(new_child)344                current_index = self.index(new_child)
345                if current_index < position:345                if current_index < position:
346                    position -= 1346                    position -= 1
347            new_child.extract()347            new_child.extract()
348        new_child.parent = self348        new_child.parent = self
349        previous_child = None349        previous_child = None
350        if position == 0:350        if position == 0:
351            new_child.previous_sibling = None351            new_child.previous_sibling = None
352            new_child.previous_element = self352            new_child.previous_element = self
353        else:353        else:
354            previous_child = self.contents[position - 1]354            previous_child = self.contents[position - 1]
355            new_child.previous_sibling = previous_child355            new_child.previous_sibling = previous_child
356            new_child.previous_sibling.next_sibling = new_child356            new_child.previous_sibling.next_sibling = new_child
357            new_child.previous_element = previous_child._last_descendant(False)357            new_child.previous_element = previous_child._last_descendant(False)
358        if new_child.previous_element is not None:358        if new_child.previous_element is not None:
359            new_child.previous_element.next_element = new_child359            new_child.previous_element.next_element = new_child
360        new_childs_last_element = new_child._last_descendant(False)360        new_childs_last_element = new_child._last_descendant(False)
361        if position >= len(self.contents):361        if position >= len(self.contents):
362            new_child.next_sibling = None362            new_child.next_sibling = None
363            parent = self363            parent = self
364            parents_next_sibling = None364            parents_next_sibling = None
365            while parents_next_sibling is None and parent is not None:365            while parents_next_sibling is None and parent is not None:
366                parents_next_sibling = parent.next_sibling366                parents_next_sibling = parent.next_sibling
367                parent = parent.parent367                parent = parent.parent
368                if parents_next_sibling is not None:368                if parents_next_sibling is not None:
369                    break369                    break
370            if parents_next_sibling is not None:370            if parents_next_sibling is not None:
371                new_childs_last_element.next_element = parents_next_sibling371                new_childs_last_element.next_element = parents_next_sibling
372            else:372            else:
373                new_childs_last_element.next_element = None373                new_childs_last_element.next_element = None
374        else:374        else:
375            next_child = self.contents[position]375            next_child = self.contents[position]
376            new_child.next_sibling = next_child376            new_child.next_sibling = next_child
377            if new_child.next_sibling is not None:377            if new_child.next_sibling is not None:
378                new_child.next_sibling.previous_sibling = new_child378                new_child.next_sibling.previous_sibling = new_child
379            new_childs_last_element.next_element = next_child379            new_childs_last_element.next_element = next_child
380        if new_childs_last_element.next_element is not None:380        if new_childs_last_element.next_element is not None:
381            new_childs_last_element.next_element.previous_element = new_childs_l381            new_childs_last_element.next_element.previous_element = new_childs_l
>ast_element>ast_element
382        self.contents.insert(position, new_child)382        self.contents.insert(position, new_child)
383383
384    def append(self, tag):384    def append(self, tag):
385        """Appends the given PageElement to the contents of this one.385        """Appends the given PageElement to the contents of this one.
386386
387        :param tag: A PageElement.387        :param tag: A PageElement.
388        """388        """
389        self.insert(len(self.contents), tag)389        self.insert(len(self.contents), tag)
390390
391    def extend(self, tags):391    def extend(self, tags):
392        """Appends the given PageElements to this one's contents.392        """Appends the given PageElements to this one's contents.
393393
394        :param tags: A list of PageElements. If a single Tag is394        :param tags: A list of PageElements. If a single Tag is
395            provided instead, this PageElement's contents will be extended395            provided instead, this PageElement's contents will be extended
396            with that Tag's contents.396            with that Tag's contents.
397        """397        """
398        if isinstance(tags, Tag):398        if isinstance(tags, Tag):
399            tags = tags.contents399            tags = tags.contents
400        if isinstance(tags, list):400        if isinstance(tags, list):
401            tags = list(tags)401            tags = list(tags)
402        for tag in tags:402        for tag in tags:
403            self.append(tag)403            self.append(tag)
404404
405    def insert_before(self, *args):405    def insert_before(self, *args):
406        """Makes the given element(s) the immediate predecessor of this one.406        """Makes the given element(s) the immediate predecessor of this one.
407407
408        All the elements will have the same parent, and the given elements408        All the elements will have the same parent, and the given elements
409        will be immediately before this one.409        will be immediately before this one.
410410
411        :param args: One or more PageElements.411        :param args: One or more PageElements.
412        """412        """
413        parent = self.parent413        parent = self.parent
414        if parent is None:414        if parent is None:
415            raise ValueError("Element has no parent, so 'before' has no meaning.415            raise ValueError("Element has no parent, so 'before' has no meaning.
>")>")
416        if any((x is self for x in args)):416        if any((x is self for x in args)):
417            raise ValueError("Can't insert an element before itself.")417            raise ValueError("Can't insert an element before itself.")
418        for predecessor in args:418        for predecessor in args:
419            if isinstance(predecessor, PageElement):419            if isinstance(predecessor, PageElement):
420                predecessor.extract()420                predecessor.extract()
421            index = parent.index(self)421            index = parent.index(self)
422            parent.insert(index, predecessor)422            parent.insert(index, predecessor)
423423
424    def insert_after(self, *args):424    def insert_after(self, *args):
425        """Makes the given element(s) the immediate successor of this one.425        """Makes the given element(s) the immediate successor of this one.
426426
427        The elements will have the same parent, and the given elements427        The elements will have the same parent, and the given elements
428        will be immediately after this one.428        will be immediately after this one.
429429
430        :param args: One or more PageElements.430        :param args: One or more PageElements.
431        """431        """
432        parent = self.parent432        parent = self.parent
433        if parent is None:433        if parent is None:
434            raise ValueError("Element has no parent, so 'after' has no meaning."434            raise ValueError("Element has no parent, so 'after' has no meaning."
>)>)
435        if any((x is self for x in args)):435        if any((x is self for x in args)):
436            raise ValueError("Can't insert an element after itself.")436            raise ValueError("Can't insert an element after itself.")
437        offset = 0437        offset = 0
438        for successor in args:438        for successor in args:
439            if isinstance(successor, PageElement):439            if isinstance(successor, PageElement):
440                successor.extract()440                successor.extract()
441            index = parent.index(self)441            index = parent.index(self)
442            parent.insert(index + 1 - offset, successor)442            parent.insert(index + 1 - offset, successor)
443            offset += 1443            offset += 1
444444
445    def find_next(self, name=None, attrs={}, string=None, **kwargs):445    def find_next(self, name=None, attrs={}, string=None, **kwargs):
446        """Find the first PageElement that matches the given criteria and446        """Find the first PageElement that matches the given criteria and
447        appears later in the document than this PageElement.447        appears later in the document than this PageElement.
448448
449        All find_* methods take a common set of arguments. See the online449        All find_* methods take a common set of arguments. See the online
450        documentation for detailed explanations.450        documentation for detailed explanations.
451451
452        :param name: A filter on tag name.452        :param name: A filter on tag name.
453        :param attrs: A dictionary of filters on attribute values.453        :param attrs: A dictionary of filters on attribute values.
454        :param string: A filter for a NavigableString with specific text.454        :param string: A filter for a NavigableString with specific text.
455        :kwargs: A dictionary of filters on attribute values.455        :kwargs: A dictionary of filters on attribute values.
456        :return: A PageElement.456        :return: A PageElement.
457        :rtype: bs4.element.Tag | bs4.element.NavigableString457        :rtype: bs4.element.Tag | bs4.element.NavigableString
458        """458        """
459        return self._find_one(self.find_all_next, name, attrs, string, **kwargs)459        return self._find_one(self.find_all_next, name, attrs, string, **kwargs)
460    findNext = find_next460    findNext = find_next
461461
462    def find_all_next(self, name=None, attrs={}, string=None, limit=None, **kwar462    def find_all_next(self, name=None, attrs={}, string=None, limit=None, **kwar
>gs):>gs):
463        """Find all PageElements that match the given criteria and appear463        """Find all PageElements that match the given criteria and appear
464        later in the document than this PageElement.464        later in the document than this PageElement.
465465
466        All find_* methods take a common set of arguments. See the online466        All find_* methods take a common set of arguments. See the online
467        documentation for detailed explanations.467        documentation for detailed explanations.
468468
469        :param name: A filter on tag name.469        :param name: A filter on tag name.
470        :param attrs: A dictionary of filters on attribute values.470        :param attrs: A dictionary of filters on attribute values.
471        :param string: A filter for a NavigableString with specific text.471        :param string: A filter for a NavigableString with specific text.
472        :param limit: Stop looking after finding this many results.472        :param limit: Stop looking after finding this many results.
473        :kwargs: A dictionary of filters on attribute values.473        :kwargs: A dictionary of filters on attribute values.
474        :return: A ResultSet containing PageElements.474        :return: A ResultSet containing PageElements.
475        """475        """
476        _stacklevel = kwargs.pop('_stacklevel', 2)476        _stacklevel = kwargs.pop('_stacklevel', 2)
477        return self._find_all(name, attrs, string, limit, self.next_elements, _s477        return self._find_all(name, attrs, string, limit, self.next_elements, _s
>tacklevel=_stacklevel + 1, **kwargs)>tacklevel=_stacklevel + 1, **kwargs)
478    findAllNext = find_all_next478    findAllNext = find_all_next
479479
480    def find_next_sibling(self, name=None, attrs={}, string=None, **kwargs):480    def find_next_sibling(self, name=None, attrs={}, string=None, **kwargs):
481        """Find the closest sibling to this PageElement that matches the481        """Find the closest sibling to this PageElement that matches the
482        given criteria and appears later in the document.482        given criteria and appears later in the document.
483483
484        All find_* methods take a common set of arguments. See the484        All find_* methods take a common set of arguments. See the
485        online documentation for detailed explanations.485        online documentation for detailed explanations.
486486
487        :param name: A filter on tag name.487        :param name: A filter on tag name.
488        :param attrs: A dictionary of filters on attribute values.488        :param attrs: A dictionary of filters on attribute values.
489        :param string: A filter for a NavigableString with specific text.489        :param string: A filter for a NavigableString with specific text.
490        :kwargs: A dictionary of filters on attribute values.490        :kwargs: A dictionary of filters on attribute values.
491        :return: A PageElement.491        :return: A PageElement.
492        :rtype: bs4.element.Tag | bs4.element.NavigableString492        :rtype: bs4.element.Tag | bs4.element.NavigableString
493        """493        """
494        return self._find_one(self.find_next_siblings, name, attrs, string, **kw494        return self._find_one(self.find_next_siblings, name, attrs, string, **kw
>args)>args)
495    findNextSibling = find_next_sibling495    findNextSibling = find_next_sibling
496496
497    def find_next_siblings(self, name=None, attrs={}, string=None, limit=None, *497    def find_next_siblings(self, name=None, attrs={}, string=None, limit=None, *
>*kwargs):>*kwargs):
498        """Find all siblings of this PageElement that match the given criteria498        """Find all siblings of this PageElement that match the given criteria
499        and appear later in the document.499        and appear later in the document.
500500
501        All find_* methods take a common set of arguments. See the online501        All find_* methods take a common set of arguments. See the online
502        documentation for detailed explanations.502        documentation for detailed explanations.
503503
504        :param name: A filter on tag name.504        :param name: A filter on tag name.
505        :param attrs: A dictionary of filters on attribute values.505        :param attrs: A dictionary of filters on attribute values.
506        :param string: A filter for a NavigableString with specific text.506        :param string: A filter for a NavigableString with specific text.
507        :param limit: Stop looking after finding this many results.507        :param limit: Stop looking after finding this many results.
508        :kwargs: A dictionary of filters on attribute values.508        :kwargs: A dictionary of filters on attribute values.
509        :return: A ResultSet of PageElements.509        :return: A ResultSet of PageElements.
510        :rtype: bs4.element.ResultSet510        :rtype: bs4.element.ResultSet
511        """511        """
512        _stacklevel = kwargs.pop('_stacklevel', 2)512        _stacklevel = kwargs.pop('_stacklevel', 2)
513        return self._find_all(name, attrs, string, limit, self.next_siblings, _s513        return self._find_all(name, attrs, string, limit, self.next_siblings, _s
>tacklevel=_stacklevel + 1, **kwargs)>tacklevel=_stacklevel + 1, **kwargs)
514    findNextSiblings = find_next_siblings514    findNextSiblings = find_next_siblings
515    fetchNextSiblings = find_next_siblings515    fetchNextSiblings = find_next_siblings
516516
517    def find_previous(self, name=None, attrs={}, string=None, **kwargs):517    def find_previous(self, name=None, attrs={}, string=None, **kwargs):
518        """Look backwards in the document from this PageElement and find the518        """Look backwards in the document from this PageElement and find the
519        first PageElement that matches the given criteria.519        first PageElement that matches the given criteria.
520520
521        All find_* methods take a common set of arguments. See the online521        All find_* methods take a common set of arguments. See the online
522        documentation for detailed explanations.522        documentation for detailed explanations.
523523
524        :param name: A filter on tag name.524        :param name: A filter on tag name.
525        :param attrs: A dictionary of filters on attribute values.525        :param attrs: A dictionary of filters on attribute values.
526        :param string: A filter for a NavigableString with specific text.526        :param string: A filter for a NavigableString with specific text.
527        :kwargs: A dictionary of filters on attribute values.527        :kwargs: A dictionary of filters on attribute values.
528        :return: A PageElement.528        :return: A PageElement.
529        :rtype: bs4.element.Tag | bs4.element.NavigableString529        :rtype: bs4.element.Tag | bs4.element.NavigableString
530        """530        """
531        return self._find_one(self.find_all_previous, name, attrs, string, **kwa531        return self._find_one(self.find_all_previous, name, attrs, string, **kwa
>rgs)>rgs)
532    findPrevious = find_previous532    findPrevious = find_previous
533533
534    def find_all_previous(self, name=None, attrs={}, string=None, limit=None, **534    def find_all_previous(self, name=None, attrs={}, string=None, limit=None, **
>kwargs):>kwargs):
535        """Look backwards in the document from this PageElement and find all535        """Look backwards in the document from this PageElement and find all
536        PageElements that match the given criteria.536        PageElements that match the given criteria.
537537
538        All find_* methods take a common set of arguments. See the online538        All find_* methods take a common set of arguments. See the online
539        documentation for detailed explanations.539        documentation for detailed explanations.
540540
541        :param name: A filter on tag name.541        :param name: A filter on tag name.
542        :param attrs: A dictionary of filters on attribute values.542        :param attrs: A dictionary of filters on attribute values.
543        :param string: A filter for a NavigableString with specific text.543        :param string: A filter for a NavigableString with specific text.
544        :param limit: Stop looking after finding this many results.544        :param limit: Stop looking after finding this many results.
545        :kwargs: A dictionary of filters on attribute values.545        :kwargs: A dictionary of filters on attribute values.
546        :return: A ResultSet of PageElements.546        :return: A ResultSet of PageElements.
547        :rtype: bs4.element.ResultSet547        :rtype: bs4.element.ResultSet
548        """548        """
549        _stacklevel = kwargs.pop('_stacklevel', 2)549        _stacklevel = kwargs.pop('_stacklevel', 2)
550        return self._find_all(name, attrs, string, limit, self.previous_elements550        return self._find_all(name, attrs, string, limit, self.previous_elements
>, _stacklevel=_stacklevel + 1, **kwargs)>, _stacklevel=_stacklevel + 1, **kwargs)
551    findAllPrevious = find_all_previous551    findAllPrevious = find_all_previous
552    fetchPrevious = find_all_previous552    fetchPrevious = find_all_previous
553553
554    def find_previous_sibling(self, name=None, attrs={}, string=None, **kwargs):554    def find_previous_sibling(self, name=None, attrs={}, string=None, **kwargs):
555        """Returns the closest sibling to this PageElement that matches the555        """Returns the closest sibling to this PageElement that matches the
556        given criteria and appears earlier in the document.556        given criteria and appears earlier in the document.
557557
558        All find_* methods take a common set of arguments. See the online558        All find_* methods take a common set of arguments. See the online
559        documentation for detailed explanations.559        documentation for detailed explanations.
560560
561        :param name: A filter on tag name.561        :param name: A filter on tag name.
562        :param attrs: A dictionary of filters on attribute values.562        :param attrs: A dictionary of filters on attribute values.
563        :param string: A filter for a NavigableString with specific text.563        :param string: A filter for a NavigableString with specific text.
564        :kwargs: A dictionary of filters on attribute values.564        :kwargs: A dictionary of filters on attribute values.
565        :return: A PageElement.565        :return: A PageElement.
566        :rtype: bs4.element.Tag | bs4.element.NavigableString566        :rtype: bs4.element.Tag | bs4.element.NavigableString
567        """567        """
568        return self._find_one(self.find_previous_siblings, name, attrs, string, 568        return self._find_one(self.find_previous_siblings, name, attrs, string, 
>**kwargs)>**kwargs)
569    findPreviousSibling = find_previous_sibling569    findPreviousSibling = find_previous_sibling
570570
571    def find_previous_siblings(self, name=None, attrs={}, string=None, limit=Non571    def find_previous_siblings(self, name=None, attrs={}, string=None, limit=Non
>e, **kwargs):>e, **kwargs):
572        """Returns all siblings to this PageElement that match the572        """Returns all siblings to this PageElement that match the
573        given criteria and appear earlier in the document.573        given criteria and appear earlier in the document.
574574
575        All find_* methods take a common set of arguments. See the online575        All find_* methods take a common set of arguments. See the online
576        documentation for detailed explanations.576        documentation for detailed explanations.
577577
578        :param name: A filter on tag name.578        :param name: A filter on tag name.
579        :param attrs: A dictionary of filters on attribute values.579        :param attrs: A dictionary of filters on attribute values.
580        :param string: A filter for a NavigableString with specific text.580        :param string: A filter for a NavigableString with specific text.
581        :param limit: Stop looking after finding this many results.581        :param limit: Stop looking after finding this many results.
582        :kwargs: A dictionary of filters on attribute values.582        :kwargs: A dictionary of filters on attribute values.
583        :return: A ResultSet of PageElements.583        :return: A ResultSet of PageElements.
584        :rtype: bs4.element.ResultSet584        :rtype: bs4.element.ResultSet
585        """585        """
586        _stacklevel = kwargs.pop('_stacklevel', 2)586        _stacklevel = kwargs.pop('_stacklevel', 2)
587        return self._find_all(name, attrs, string, limit, self.previous_siblings587        return self._find_all(name, attrs, string, limit, self.previous_siblings
>, _stacklevel=_stacklevel + 1, **kwargs)>, _stacklevel=_stacklevel + 1, **kwargs)
588    findPreviousSiblings = find_previous_siblings588    findPreviousSiblings = find_previous_siblings
589    fetchPreviousSiblings = find_previous_siblings589    fetchPreviousSiblings = find_previous_siblings
590590
591    def find_parent(self, name=None, attrs={}, **kwargs):591    def find_parent(self, name=None, attrs={}, **kwargs):
592        """Find the closest parent of this PageElement that matches the given592        """Find the closest parent of this PageElement that matches the given
593        criteria.593        criteria.
594594
595        All find_* methods take a common set of arguments. See the online595        All find_* methods take a common set of arguments. See the online
596        documentation for detailed explanations.596        documentation for detailed explanations.
597597
598        :param name: A filter on tag name.598        :param name: A filter on tag name.
599        :param attrs: A dictionary of filters on attribute values.599        :param attrs: A dictionary of filters on attribute values.
600        :kwargs: A dictionary of filters on attribute values.600        :kwargs: A dictionary of filters on attribute values.
601601
602        :return: A PageElement.602        :return: A PageElement.
603        :rtype: bs4.element.Tag | bs4.element.NavigableString603        :rtype: bs4.element.Tag | bs4.element.NavigableString
604        """604        """
605        r = None605        r = None
606        l = self.find_parents(name, attrs, 1, _stacklevel=3, **kwargs)606        l = self.find_parents(name, attrs, 1, _stacklevel=3, **kwargs)
607        if l:607        if l:
608            r = l[0]608            r = l[0]
609        return r609        return r
610    findParent = find_parent610    findParent = find_parent
611611
612    def find_parents(self, name=None, attrs={}, limit=None, **kwargs):612    def find_parents(self, name=None, attrs={}, limit=None, **kwargs):
613        """Find all parents of this PageElement that match the given criteria.613        """Find all parents of this PageElement that match the given criteria.
614614
615        All find_* methods take a common set of arguments. See the online615        All find_* methods take a common set of arguments. See the online
616        documentation for detailed explanations.616        documentation for detailed explanations.
617617
618        :param name: A filter on tag name.618        :param name: A filter on tag name.
619        :param attrs: A dictionary of filters on attribute values.619        :param attrs: A dictionary of filters on attribute values.
620        :param limit: Stop looking after finding this many results.620        :param limit: Stop looking after finding this many results.
621        :kwargs: A dictionary of filters on attribute values.621        :kwargs: A dictionary of filters on attribute values.
622622
623        :return: A PageElement.623        :return: A PageElement.
624        :rtype: bs4.element.Tag | bs4.element.NavigableString624        :rtype: bs4.element.Tag | bs4.element.NavigableString
625        """625        """
626        _stacklevel = kwargs.pop('_stacklevel', 2)626        _stacklevel = kwargs.pop('_stacklevel', 2)
627        return self._find_all(name, attrs, None, limit, self.parents, _stackleve627        return self._find_all(name, attrs, None, limit, self.parents, _stackleve
>l=_stacklevel + 1, **kwargs)>l=_stacklevel + 1, **kwargs)
628    findParents = find_parents628    findParents = find_parents
629    fetchParents = find_parents629    fetchParents = find_parents
630630
631    @property631    @property
632    def next(self):632    def next(self):
633        """The PageElement, if any, that was parsed just after this one.633        """The PageElement, if any, that was parsed just after this one.
634634
635        :return: A PageElement.635        :return: A PageElement.
636        :rtype: bs4.element.Tag | bs4.element.NavigableString636        :rtype: bs4.element.Tag | bs4.element.NavigableString
637        """637        """
638        return self.next_element638        return self.next_element
639639
640    @property640    @property
641    def previous(self):641    def previous(self):
642        """The PageElement, if any, that was parsed just before this one.642        """The PageElement, if any, that was parsed just before this one.
643643
644        :return: A PageElement.644        :return: A PageElement.
645        :rtype: bs4.element.Tag | bs4.element.NavigableString645        :rtype: bs4.element.Tag | bs4.element.NavigableString
646        """646        """
647        return self.previous_element647        return self.previous_element
648648
649    def _find_one(self, method, name, attrs, string, **kwargs):649    def _find_one(self, method, name, attrs, string, **kwargs):
650        r = None650        r = None
651        l = method(name, attrs, string, 1, _stacklevel=4, **kwargs)651        l = method(name, attrs, string, 1, _stacklevel=4, **kwargs)
652        if l:652        if l:
653            r = l[0]653            r = l[0]
654        return r654        return r
655655
656    def _find_all(self, name, attrs, string, limit, generator, **kwargs):656    def _find_all(self, name, attrs, string, limit, generator, **kwargs):
657        """Iterates over a generator looking for things that match."""657        """Iterates over a generator looking for things that match."""
658        _stacklevel = kwargs.pop('_stacklevel', 3)658        _stacklevel = kwargs.pop('_stacklevel', 3)
659        if string is None and 'text' in kwargs:659        if string is None and 'text' in kwargs:
660            string = kwargs.pop('text')660            string = kwargs.pop('text')
661            warnings.warn("The 'text' argument to find()-type methods is depreca661            warnings.warn("The 'text' argument to find()-type methods is depreca
>ted. Use 'string' instead.", DeprecationWarning, stacklevel=_stacklevel)>ted. Use 'string' instead.", DeprecationWarning, stacklevel=_stacklevel)
662        if isinstance(name, SoupStrainer):662        if isinstance(name, SoupStrainer):
663            strainer = name663            strainer = name
664        else:664        else:
665            strainer = SoupStrainer(name, attrs, string, **kwargs)665            strainer = SoupStrainer(name, attrs, string, **kwargs)
666        if string is None and (not limit) and (not attrs) and (not kwargs):666        if string is None and (not limit) and (not attrs) and (not kwargs):
667            if name is True or name is None:667            if name is True or name is None:
668                result = (element for element in generator if isinstance(element668                result = (element for element in generator if isinstance(element
>, Tag))>, Tag))
669                return ResultSet(strainer, result)669                return ResultSet(strainer, result)
670            elif isinstance(name, str):670            elif isinstance(name, str):
671                if name.count(':') == 1:671                if name.count(':') == 1:
672                    (prefix, local_name) = name.split(':', 1)672                    (prefix, local_name) = name.split(':', 1)
673                else:673                else:
674                    prefix = None674                    prefix = None
675                    local_name = name675                    local_name = name
676                result = (element for element in generator if isinstance(element676                result = (element for element in generator if isinstance(element
>, Tag) and element.name == name or (element.name == local_name and (prefix is No>, Tag) and element.name == name or (element.name == local_name and (prefix is No
>ne or element.prefix == prefix)))>ne or element.prefix == prefix)))
677                return ResultSet(strainer, result)677                return ResultSet(strainer, result)
678        results = ResultSet(strainer)678        results = ResultSet(strainer)
679        while True:679        while True:
680            try:680            try:
681                i = next(generator)681                i = next(generator)
682            except StopIteration:682            except StopIteration:
683                break683                break
684            if i:684            if i:
685                found = strainer.search(i)685                found = strainer.search(i)
686                if found:686                if found:
687                    results.append(found)687                    results.append(found)
688                    if limit and len(results) < limit:688                    if limit and len(results) < limit:
689                        break689                        break
690        return results690        return results
691691
692    @property692    @property
693    def next_elements(self):693    def next_elements(self):
694        """All PageElements that were parsed after this one.694        """All PageElements that were parsed after this one.
695695
696        :yield: A sequence of PageElements.696        :yield: A sequence of PageElements.
697        """697        """
698        i = self.next_element698        i = self.next_element
699        while i is not None:699        while i is not None:
700            yield i700            yield i
701            i = i.next_element701            i = i.next_element
702702
703    @property703    @property
704    def next_siblings(self):704    def next_siblings(self):
705        """All PageElements that are siblings of this one but were parsed705        """All PageElements that are siblings of this one but were parsed
706        later.706        later.
707707
708        :yield: A sequence of PageElements.708        :yield: A sequence of PageElements.
709        """709        """
710        i = self.next_sibling710        i = self.next_sibling
711        while i is not None:711        while i is not None:
712            yield i712            yield i
713            i = i.next_sibling713            i = i.next_sibling
714714
715    @property715    @property
716    def previous_elements(self):716    def previous_elements(self):
717        """All PageElements that were parsed before this one.717        """All PageElements that were parsed before this one.
718718
719        :yield: A sequence of PageElements.719        :yield: A sequence of PageElements.
720        """720        """
721        i = self.previous_element721        i = self.previous_element
722        while i is not None:722        while i is not None:
723            yield i723            yield i
724            i = i.previous_element724            i = i.previous_element
725725
726    @property726    @property
727    def previous_siblings(self):727    def previous_siblings(self):
728        """All PageElements that are siblings of this one but were parsed728        """All PageElements that are siblings of this one but were parsed
729        earlier.729        earlier.
730730
731        :yield: A sequence of PageElements.731        :yield: A sequence of PageElements.
732        """732        """
733        i = self.previous_sibling733        i = self.previous_sibling
734        while i is not None:734        while i is not None:
735            yield i735            yield i
736            i = i.previous_sibling736            i = i.previous_sibling
737737
738    @property738    @property
739    def parents(self):739    def parents(self):
740        """All PageElements that are parents of this PageElement.740        """All PageElements that are parents of this PageElement.
741741
742        :yield: A sequence of PageElements.742        :yield: A sequence of PageElements.
743        """743        """
744        i = self.parent744        i = self.parent
745        while i is not None:745        while i is not None:
746            yield i746            yield i
747            i = i.parent747            i = i.parent
748748
749    @property749    @property
750    def decomposed(self):750    def decomposed(self):
751        """Check whether a PageElement has been decomposed.751        """Check whether a PageElement has been decomposed.
752752
753        :rtype: bool753        :rtype: bool
754        """754        """
755        return getattr(self, '_decomposed', False) or False755        return getattr(self, '_decomposed', False) or False
756756
757    def nextGenerator(self):757    def nextGenerator(self):
758        return self.next_elements758        return self.next_elements
759759
760    def nextSiblingGenerator(self):760    def nextSiblingGenerator(self):
761        return self.next_siblings761        return self.next_siblings
762762
763    def previousGenerator(self):763    def previousGenerator(self):
764        return self.previous_elements764        return self.previous_elements
765765
766    def previousSiblingGenerator(self):766    def previousSiblingGenerator(self):
767        return self.previous_siblings767        return self.previous_siblings
768768
769    def parentGenerator(self):769    def parentGenerator(self):
770        return self.parents770        return self.parents
771771
772class NavigableString(str, PageElement):772class NavigableString(str, PageElement):
773    """A Python Unicode string that is part of a parse tree.773    """A Python Unicode string that is part of a parse tree.
774774
775    When Beautiful Soup parses the markup <b>penguin</b>, it will775    When Beautiful Soup parses the markup <b>penguin</b>, it will
776    create a NavigableString for the string "penguin".776    create a NavigableString for the string "penguin".
777    """777    """
778    PREFIX = ''778    PREFIX = ''
779    SUFFIX = ''779    SUFFIX = ''
780    known_xml = None780    known_xml = None
781781
782    def __new__(cls, value):782    def __new__(cls, value):
783        """Create a new NavigableString.783        """Create a new NavigableString.
784784
785        When unpickling a NavigableString, this method is called with785        When unpickling a NavigableString, this method is called with
786        the string in DEFAULT_OUTPUT_ENCODING. That encoding needs to be786        the string in DEFAULT_OUTPUT_ENCODING. That encoding needs to be
787        passed in to the superclass's __new__ or the superclass won't know787        passed in to the superclass's __new__ or the superclass won't know
788        how to handle non-ASCII characters.788        how to handle non-ASCII characters.
789        """789        """
790        if isinstance(value, str):790        if isinstance(value, str):
791            u = str.__new__(cls, value)791            u = str.__new__(cls, value)
792        else:792        else:
793            u = str.__new__(cls, value, DEFAULT_OUTPUT_ENCODING)793            u = str.__new__(cls, value, DEFAULT_OUTPUT_ENCODING)
794        u.setup()794        u.setup()
795        return u795        return u
796796
797    def __copy__(self):797    def __copy__(self):
798        """A copy of a NavigableString has the same contents and class798        """A copy of a NavigableString has the same contents and class
799        as the original, but it is not connected to the parse tree.799        as the original, but it is not connected to the parse tree.
800        """800        """
801        return type(self)(self)801        return type(self)(self)
802802
803    def __getnewargs__(self):803    def __getnewargs__(self):
804        return (str(self),)804        return (str(self),)
805805
806    def __getattr__(self, attr):806    def __getattr__(self, attr):
807        """text.string gives you text. This is for backwards807        """text.string gives you text. This is for backwards
808        compatibility for Navigable*String, but for CData* it lets you808        compatibility for Navigable*String, but for CData* it lets you
809        get the string without the CData wrapper."""809        get the string without the CData wrapper."""
810        if attr == 'string':810        if attr == 'string':
811            return self811            return self
812        else:812        else:
813            raise AttributeError("'%s' object has no attribute '%s'" % (self.__c813            raise AttributeError("'%s' object has no attribute '%s'" % (self.__c
>lass__.__name__, attr))>lass__.__name__, attr))
814814
815    def output_ready(self, formatter='minimal'):815    def output_ready(self, formatter='minimal'):
816        """Run the string through the provided formatter.816        """Run the string through the provided formatter.
817817
818        :param formatter: A Formatter object, or a string naming one of the stan818        :param formatter: A Formatter object, or a string naming one of the stan
>dard formatters.>dard formatters.
819        """819        """
820        output = self.format_string(self, formatter)820        output = self.format_string(self, formatter)
821        return self.PREFIX + output + self.SUFFIX821        return self.PREFIX + output + self.SUFFIX
822822
823    @property823    @property
824    def name(self):824    def name(self):
825        """Since a NavigableString is not a Tag, it has no .name.825        """Since a NavigableString is not a Tag, it has no .name.
826826
827        This property is implemented so that code like this doesn't crash827        This property is implemented so that code like this doesn't crash
828        when run on a mixture of Tag and NavigableString objects:828        when run on a mixture of Tag and NavigableString objects:
829            [x.name for x in tag.children]829            [x.name for x in tag.children]
830        """830        """
831        return None831        return None
832832
833    @name.setter833    @name.setter
834    def name(self, name):834    def name(self, name):
835        """Prevent NavigableString.name from ever being set."""835        """Prevent NavigableString.name from ever being set."""
836        raise AttributeError('A NavigableString cannot be given a name.')836        raise AttributeError('A NavigableString cannot be given a name.')
837837
838    def _all_strings(self, strip=False, types=PageElement.default):838    def _all_strings(self, strip=False, types=PageElement.default):
839        """Yield all strings of certain classes, possibly stripping them.839        """Yield all strings of certain classes, possibly stripping them.
840840
841        This makes it easy for NavigableString to implement methods841        This makes it easy for NavigableString to implement methods
842        like get_text() as conveniences, creating a consistent842        like get_text() as conveniences, creating a consistent
843        text-extraction API across all PageElements.843        text-extraction API across all PageElements.
844844
845        :param strip: If True, all strings will be stripped before being845        :param strip: If True, all strings will be stripped before being
846            yielded.846            yielded.
847847
848        :param types: A tuple of NavigableString subclasses. If this848        :param types: A tuple of NavigableString subclasses. If this
849            NavigableString isn't one of those subclasses, the849            NavigableString isn't one of those subclasses, the
850            sequence will be empty. By default, the subclasses850            sequence will be empty. By default, the subclasses
851            considered are NavigableString and CData objects. That851            considered are NavigableString and CData objects. That
852            means no comments, processing instructions, etc.852            means no comments, processing instructions, etc.
853853
854        :yield: A sequence that either contains this string, or is empty.854        :yield: A sequence that either contains this string, or is empty.
855855
856        """856        """
857        if types is self.default:857        if types is self.default:
858            types = Tag.DEFAULT_INTERESTING_STRING_TYPES858            types = Tag.DEFAULT_INTERESTING_STRING_TYPES
859        my_type = type(self)859        my_type = type(self)
860        if not types is not None:860        if not types is not None:
861            if isinstance(types, type):861            if isinstance(types, type):
862                if my_type is not types:862                if my_type is not types:
863                    return863                    return
864            elif my_type not in types:864            elif my_type not in types:
865                return865                return
866        value = self866        value = self
867        if strip:867        if strip:
868            value = value.strip()868            value = value.strip()
869        if len(value) > 0:869        if len(value) > 0:
870            yield value870            yield value
871    strings = property(_all_strings)871    strings = property(_all_strings)
872872
873class PreformattedString(NavigableString):873class PreformattedString(NavigableString):
874    """A NavigableString not subject to the normal formatting rules.874    """A NavigableString not subject to the normal formatting rules.
875875
876    This is an abstract class used for special kinds of strings such876    This is an abstract class used for special kinds of strings such
877    as comments (the Comment class) and CDATA blocks (the CData877    as comments (the Comment class) and CDATA blocks (the CData
878    class).878    class).
879    """879    """
880    PREFIX = ''880    PREFIX = ''
881    SUFFIX = ''881    SUFFIX = ''
882882
883    def output_ready(self, formatter=None):883    def output_ready(self, formatter=None):
884        """Make this string ready for output by adding any subclass-specific884        """Make this string ready for output by adding any subclass-specific
885            prefix or suffix.885            prefix or suffix.
886886
887        :param formatter: A Formatter object, or a string naming one887        :param formatter: A Formatter object, or a string naming one
888            of the standard formatters. The string will be passed into the888            of the standard formatters. The string will be passed into the
889            Formatter, but only to trigger any side effects: the return889            Formatter, but only to trigger any side effects: the return
890            value is ignored.890            value is ignored.
891891
892        :return: The string, with any subclass-specific prefix and892        :return: The string, with any subclass-specific prefix and
893           suffix added on.893           suffix added on.
894        """894        """
895        if formatter is not None:895        if formatter is not None:
896            ignore = self.format_string(self, formatter)896            ignore = self.format_string(self, formatter)
897        return self.PREFIX + self + self.SUFFIX897        return self.PREFIX + self + self.SUFFIX
898898
899class CData(PreformattedString):899class CData(PreformattedString):
900    """A CDATA block."""900    """A CDATA block."""
901    PREFIX = '<![CDATA['901    PREFIX = '<![CDATA['
902    SUFFIX = ']]>'902    SUFFIX = ']]>'
903903
904class ProcessingInstruction(PreformattedString):904class ProcessingInstruction(PreformattedString):
905    """A SGML processing instruction."""905    """A SGML processing instruction."""
906    PREFIX = '<?'906    PREFIX = '<?'
907    SUFFIX = '>'907    SUFFIX = '>'
908908
909class XMLProcessingInstruction(ProcessingInstruction):909class XMLProcessingInstruction(ProcessingInstruction):
910    """An XML processing instruction."""910    """An XML processing instruction."""
911    PREFIX = '<?'911    PREFIX = '<?'
912    SUFFIX = '?>'912    SUFFIX = '?>'
913913
914class Comment(PreformattedString):914class Comment(PreformattedString):
915    """An HTML or XML comment."""915    """An HTML or XML comment."""
916    PREFIX = '<!--'916    PREFIX = '<!--'
917    SUFFIX = '-->'917    SUFFIX = '-->'
918918
919class Declaration(PreformattedString):919class Declaration(PreformattedString):
920    """An XML declaration."""920    """An XML declaration."""
921    PREFIX = '<?'921    PREFIX = '<?'
922    SUFFIX = '?>'922    SUFFIX = '?>'
923923
924class Doctype(PreformattedString):924class Doctype(PreformattedString):
925    """A document type declaration."""925    """A document type declaration."""
926926
927    @classmethod927    @classmethod
928    def for_name_and_ids(cls, name, pub_id, system_id):928    def for_name_and_ids(cls, name, pub_id, system_id):
929        """Generate an appropriate document type declaration for a given929        """Generate an appropriate document type declaration for a given
930        public ID and system ID.930        public ID and system ID.
931931
932        :param name: The name of the document's root element, e.g. 'html'.932        :param name: The name of the document's root element, e.g. 'html'.
933        :param pub_id: The Formal Public Identifier for this document type,933        :param pub_id: The Formal Public Identifier for this document type,
934            e.g. '-//W3C//DTD XHTML 1.1//EN'934            e.g. '-//W3C//DTD XHTML 1.1//EN'
935        :param system_id: The system identifier for this document type,935        :param system_id: The system identifier for this document type,
936            e.g. 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'936            e.g. 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'
937937
938        :return: A Doctype.938        :return: A Doctype.
939        """939        """
940        value = name or ''940        value = name or ''
941        if pub_id is not None:941        if pub_id is not None:
942            value += ' PUBLIC "%s"' % pub_id942            value += ' PUBLIC "%s"' % pub_id
943            if system_id is not None:943            if system_id is not None:
944                value += ' "%s"' % system_id944                value += ' "%s"' % system_id
945        elif system_id is not None:945        elif system_id is not None:
n946            value += ' SYSTEM "%s"' % system_idn946            value -= ' SYSTEM "%s"' % system_id
947        return Doctype(value)947        return Doctype(value)
948    PREFIX = '<!DOCTYPE '948    PREFIX = '<!DOCTYPE '
949    SUFFIX = '>\n'949    SUFFIX = '>\n'
950950
951class Stylesheet(NavigableString):951class Stylesheet(NavigableString):
952    """A NavigableString representing an stylesheet (probably952    """A NavigableString representing an stylesheet (probably
953    CSS).953    CSS).
954954
955    Used to distinguish embedded stylesheets from textual content.955    Used to distinguish embedded stylesheets from textual content.
956    """956    """
957    pass957    pass
958958
959class Script(NavigableString):959class Script(NavigableString):
960    """A NavigableString representing an executable script (probably960    """A NavigableString representing an executable script (probably
961    Javascript).961    Javascript).
962962
963    Used to distinguish executable code from textual content.963    Used to distinguish executable code from textual content.
964    """964    """
965    pass965    pass
966966
967class TemplateString(NavigableString):967class TemplateString(NavigableString):
968    """A NavigableString representing a string found inside an HTML968    """A NavigableString representing a string found inside an HTML
969    template embedded in a larger document.969    template embedded in a larger document.
970970
971    Used to distinguish such strings from the main body of the document.971    Used to distinguish such strings from the main body of the document.
972    """972    """
973    pass973    pass
974974
975class RubyTextString(NavigableString):975class RubyTextString(NavigableString):
976    """A NavigableString representing the contents of the <rt> HTML976    """A NavigableString representing the contents of the <rt> HTML
977    element.977    element.
978978
979    https://dev.w3.org/html5/spec-LC/text-level-semantics.html#the-rt-element979    https://dev.w3.org/html5/spec-LC/text-level-semantics.html#the-rt-element
980980
981    Can be used to distinguish such strings from the strings they're981    Can be used to distinguish such strings from the strings they're
982    annotating.982    annotating.
983    """983    """
984    pass984    pass
985985
986class RubyParenthesisString(NavigableString):986class RubyParenthesisString(NavigableString):
987    """A NavigableString representing the contents of the <rp> HTML987    """A NavigableString representing the contents of the <rp> HTML
988    element.988    element.
989989
990    https://dev.w3.org/html5/spec-LC/text-level-semantics.html#the-rp-element990    https://dev.w3.org/html5/spec-LC/text-level-semantics.html#the-rp-element
991    """991    """
992    pass992    pass
993993
994class Tag(PageElement):994class Tag(PageElement):
995    """Represents an HTML or XML tag that is part of a parse tree, along995    """Represents an HTML or XML tag that is part of a parse tree, along
996    with its attributes and contents.996    with its attributes and contents.
997997
998    When Beautiful Soup parses the markup <b>penguin</b>, it will998    When Beautiful Soup parses the markup <b>penguin</b>, it will
999    create a Tag object representing the <b> tag.999    create a Tag object representing the <b> tag.
1000    """1000    """
10011001
1002    def __init__(self, parser=None, builder=None, name=None, namespace=None, pre1002    def __init__(self, parser=None, builder=None, name=None, namespace=None, pre
>fix=None, attrs=None, parent=None, previous=None, is_xml=None, sourceline=None, >fix=None, attrs=None, parent=None, previous=None, is_xml=None, sourceline=None, 
>sourcepos=None, can_be_empty_element=None, cdata_list_attributes=None, preserve_>sourcepos=None, can_be_empty_element=None, cdata_list_attributes=None, preserve_
>whitespace_tags=None, interesting_string_types=None, namespaces=None):>whitespace_tags=None, interesting_string_types=None, namespaces=None):
1003        """Basic constructor.1003        """Basic constructor.
10041004
1005        :param parser: A BeautifulSoup object.1005        :param parser: A BeautifulSoup object.
1006        :param builder: A TreeBuilder.1006        :param builder: A TreeBuilder.
1007        :param name: The name of the tag.1007        :param name: The name of the tag.
1008        :param namespace: The URI of this Tag's XML namespace, if any.1008        :param namespace: The URI of this Tag's XML namespace, if any.
1009        :param prefix: The prefix for this Tag's XML namespace, if any.1009        :param prefix: The prefix for this Tag's XML namespace, if any.
1010        :param attrs: A dictionary of this Tag's attribute values.1010        :param attrs: A dictionary of this Tag's attribute values.
1011        :param parent: The PageElement to use as this Tag's parent.1011        :param parent: The PageElement to use as this Tag's parent.
1012        :param previous: The PageElement that was parsed immediately before1012        :param previous: The PageElement that was parsed immediately before
1013            this tag.1013            this tag.
1014        :param is_xml: If True, this is an XML tag. Otherwise, this is an1014        :param is_xml: If True, this is an XML tag. Otherwise, this is an
1015            HTML tag.1015            HTML tag.
1016        :param sourceline: The line number where this tag was found in its1016        :param sourceline: The line number where this tag was found in its
1017            source document.1017            source document.
1018        :param sourcepos: The character position within `sourceline` where this1018        :param sourcepos: The character position within `sourceline` where this
1019            tag was found.1019            tag was found.
1020        :param can_be_empty_element: If True, this tag should be1020        :param can_be_empty_element: If True, this tag should be
1021            represented as <tag/>. If False, this tag should be represented1021            represented as <tag/>. If False, this tag should be represented
1022            as <tag></tag>.1022            as <tag></tag>.
1023        :param cdata_list_attributes: A list of attributes whose values should1023        :param cdata_list_attributes: A list of attributes whose values should
1024            be treated as CDATA if they ever show up on this tag.1024            be treated as CDATA if they ever show up on this tag.
1025        :param preserve_whitespace_tags: A list of tag names whose contents1025        :param preserve_whitespace_tags: A list of tag names whose contents
1026            should have their whitespace preserved.1026            should have their whitespace preserved.
1027        :param interesting_string_types: This is a NavigableString1027        :param interesting_string_types: This is a NavigableString
1028            subclass or a tuple of them. When iterating over this1028            subclass or a tuple of them. When iterating over this
1029            Tag's strings in methods like Tag.strings or Tag.get_text,1029            Tag's strings in methods like Tag.strings or Tag.get_text,
1030            these are the types of strings that are interesting enough1030            these are the types of strings that are interesting enough
1031            to be considered. The default is to consider1031            to be considered. The default is to consider
1032            NavigableString and CData the only interesting string1032            NavigableString and CData the only interesting string
1033            subtypes.1033            subtypes.
1034        :param namespaces: A dictionary mapping currently active1034        :param namespaces: A dictionary mapping currently active
1035            namespace prefixes to URIs. This can be used later to1035            namespace prefixes to URIs. This can be used later to
1036            construct CSS selectors.1036            construct CSS selectors.
1037        """1037        """
1038        if parser is None:1038        if parser is None:
1039            self.parser_class = None1039            self.parser_class = None
1040        else:1040        else:
1041            self.parser_class = parser.__class__1041            self.parser_class = parser.__class__
1042        if name is None:1042        if name is None:
1043            raise ValueError("No value provided for new tag's name.")1043            raise ValueError("No value provided for new tag's name.")
1044        self.name = name1044        self.name = name
1045        self.namespace = namespace1045        self.namespace = namespace
1046        self._namespaces = namespaces or {}1046        self._namespaces = namespaces or {}
1047        self.prefix = prefix1047        self.prefix = prefix
1048        if (not builder or builder.store_line_numbers) and (sourceline is not No1048        if (not builder or builder.store_line_numbers) and (sourceline is not No
>ne or sourcepos is not None):>ne or sourcepos is not None):
1049            self.sourceline = sourceline1049            self.sourceline = sourceline
1050            self.sourcepos = sourcepos1050            self.sourcepos = sourcepos
1051        if attrs is None:1051        if attrs is None:
1052            attrs = {}1052            attrs = {}
1053        elif attrs:1053        elif attrs:
1054            if builder is not None and builder.cdata_list_attributes:1054            if builder is not None and builder.cdata_list_attributes:
1055                attrs = builder._replace_cdata_list_attribute_values(self.name, 1055                attrs = builder._replace_cdata_list_attribute_values(self.name, 
>attrs)>attrs)
1056            else:1056            else:
1057                attrs = dict(attrs)1057                attrs = dict(attrs)
1058        else:1058        else:
1059            attrs = dict(attrs)1059            attrs = dict(attrs)
1060        if builder:1060        if builder:
1061            self.known_xml = builder.is_xml1061            self.known_xml = builder.is_xml
1062        else:1062        else:
1063            self.known_xml = is_xml1063            self.known_xml = is_xml
1064        self.attrs = attrs1064        self.attrs = attrs
1065        self.contents = []1065        self.contents = []
1066        self.setup(parent, previous)1066        self.setup(parent, previous)
1067        self.hidden = False1067        self.hidden = False
1068        if builder is None:1068        if builder is None:
1069            self.can_be_empty_element = can_be_empty_element1069            self.can_be_empty_element = can_be_empty_element
1070            self.cdata_list_attributes = cdata_list_attributes1070            self.cdata_list_attributes = cdata_list_attributes
1071            self.preserve_whitespace_tags = preserve_whitespace_tags1071            self.preserve_whitespace_tags = preserve_whitespace_tags
1072            self.interesting_string_types = interesting_string_types1072            self.interesting_string_types = interesting_string_types
1073        else:1073        else:
1074            builder.set_up_substitutions(self)1074            builder.set_up_substitutions(self)
1075            self.can_be_empty_element = builder.can_be_empty_element(name)1075            self.can_be_empty_element = builder.can_be_empty_element(name)
1076            self.cdata_list_attributes = builder.cdata_list_attributes1076            self.cdata_list_attributes = builder.cdata_list_attributes
1077            self.preserve_whitespace_tags = builder.preserve_whitespace_tags1077            self.preserve_whitespace_tags = builder.preserve_whitespace_tags
1078            if self.name in builder.string_containers:1078            if self.name in builder.string_containers:
1079                self.interesting_string_types = builder.string_containers[self.n1079                self.interesting_string_types = builder.string_containers[self.n
>ame]>ame]
1080            else:1080            else:
1081                self.interesting_string_types = self.DEFAULT_INTERESTING_STRING_1081                self.interesting_string_types = self.DEFAULT_INTERESTING_STRING_
>TYPES>TYPES
1082    parserClass = _alias('parser_class')1082    parserClass = _alias('parser_class')
10831083
1084    def __copy__(self):1084    def __copy__(self):
1085        """A copy of a Tag is a new Tag, unconnected to the parse tree.1085        """A copy of a Tag is a new Tag, unconnected to the parse tree.
1086        Its contents are a copy of the old Tag's contents.1086        Its contents are a copy of the old Tag's contents.
1087        """1087        """
1088        clone = type(self)(None, self.builder, self.name, self.namespace, self.p1088        clone = type(self)(None, self.builder, self.name, self.namespace, self.p
>refix, self.attrs, is_xml=self._is_xml, sourceline=self.sourceline, sourcepos=se>refix, self.attrs, is_xml=self._is_xml, sourceline=self.sourceline, sourcepos=se
>lf.sourcepos, can_be_empty_element=self.can_be_empty_element, cdata_list_attribu>lf.sourcepos, can_be_empty_element=self.can_be_empty_element, cdata_list_attribu
>tes=self.cdata_list_attributes, preserve_whitespace_tags=self.preserve_whitespac>tes=self.cdata_list_attributes, preserve_whitespace_tags=self.preserve_whitespac
>e_tags, interesting_string_types=self.interesting_string_types)>e_tags, interesting_string_types=self.interesting_string_types)
1089        for attr in ('can_be_empty_element', 'hidden'):1089        for attr in ('can_be_empty_element', 'hidden'):
1090            setattr(clone, attr, getattr(self, attr))1090            setattr(clone, attr, getattr(self, attr))
1091        for child in self.contents:1091        for child in self.contents:
1092            clone.append(child.__copy__())1092            clone.append(child.__copy__())
1093        return clone1093        return clone
10941094
1095    @property1095    @property
1096    def is_empty_element(self):1096    def is_empty_element(self):
1097        """Is this tag an empty-element tag? (aka a self-closing tag)1097        """Is this tag an empty-element tag? (aka a self-closing tag)
10981098
1099        A tag that has contents is never an empty-element tag.1099        A tag that has contents is never an empty-element tag.
11001100
1101        A tag that has no contents may or may not be an empty-element1101        A tag that has no contents may or may not be an empty-element
1102        tag. It depends on the builder used to create the tag. If the1102        tag. It depends on the builder used to create the tag. If the
1103        builder has a designated list of empty-element tags, then only1103        builder has a designated list of empty-element tags, then only
1104        a tag whose name shows up in that list is considered an1104        a tag whose name shows up in that list is considered an
1105        empty-element tag.1105        empty-element tag.
11061106
1107        If the builder has no designated list of empty-element tags,1107        If the builder has no designated list of empty-element tags,
1108        then any tag with no contents is an empty-element tag.1108        then any tag with no contents is an empty-element tag.
1109        """1109        """
1110        return len(self.contents) == 0 and self.can_be_empty_element1110        return len(self.contents) == 0 and self.can_be_empty_element
1111    isSelfClosing = is_empty_element1111    isSelfClosing = is_empty_element
11121112
1113    @property1113    @property
1114    def string(self):1114    def string(self):
1115        """Convenience property to get the single string within this1115        """Convenience property to get the single string within this
1116        PageElement.1116        PageElement.
11171117
1118        TODO It might make sense to have NavigableString.string return1118        TODO It might make sense to have NavigableString.string return
1119        itself.1119        itself.
11201120
1121        :return: If this element has a single string child, return1121        :return: If this element has a single string child, return
1122         value is that string. If this element has one child tag,1122         value is that string. If this element has one child tag,
1123         return value is the 'string' attribute of the child tag,1123         return value is the 'string' attribute of the child tag,
1124         recursively. If this element is itself a string, has no1124         recursively. If this element is itself a string, has no
1125         children, or has more than one child, return value is None.1125         children, or has more than one child, return value is None.
1126        """1126        """
1127        if len(self.contents) != 1:1127        if len(self.contents) != 1:
1128            return None1128            return None
1129        child = self.contents[0]1129        child = self.contents[0]
1130        if isinstance(child, NavigableString):1130        if isinstance(child, NavigableString):
1131            return child1131            return child
1132        return child.string1132        return child.string
11331133
1134    @string.setter1134    @string.setter
1135    def string(self, string):1135    def string(self, string):
1136        """Replace this PageElement's contents with `string`."""1136        """Replace this PageElement's contents with `string`."""
1137        self.clear()1137        self.clear()
1138        self.append(string.__class__(string))1138        self.append(string.__class__(string))
1139    DEFAULT_INTERESTING_STRING_TYPES = (NavigableString, CData)1139    DEFAULT_INTERESTING_STRING_TYPES = (NavigableString, CData)
11401140
1141    def _all_strings(self, strip=False, types=PageElement.default):1141    def _all_strings(self, strip=False, types=PageElement.default):
1142        """Yield all strings of certain classes, possibly stripping them.1142        """Yield all strings of certain classes, possibly stripping them.
11431143
1144        :param strip: If True, all strings will be stripped before being1144        :param strip: If True, all strings will be stripped before being
1145            yielded.1145            yielded.
11461146
1147        :param types: A tuple of NavigableString subclasses. Any strings of1147        :param types: A tuple of NavigableString subclasses. Any strings of
1148            a subclass not found in this list will be ignored. By1148            a subclass not found in this list will be ignored. By
1149            default, the subclasses considered are the ones found in1149            default, the subclasses considered are the ones found in
1150            self.interesting_string_types. If that's not specified,1150            self.interesting_string_types. If that's not specified,
1151            only NavigableString and CData objects will be1151            only NavigableString and CData objects will be
1152            considered. That means no comments, processing1152            considered. That means no comments, processing
1153            instructions, etc.1153            instructions, etc.
11541154
1155        :yield: A sequence of strings.1155        :yield: A sequence of strings.
11561156
1157        """1157        """
1158        if types is self.default:1158        if types is self.default:
1159            types = self.interesting_string_types1159            types = self.interesting_string_types
1160        for descendant in self.descendants:1160        for descendant in self.descendants:
1161            if types is None and (not isinstance(descendant, NavigableString)):1161            if types is None and (not isinstance(descendant, NavigableString)):
1162                continue1162                continue
1163            descendant_type = type(descendant)1163            descendant_type = type(descendant)
1164            if isinstance(types, type):1164            if isinstance(types, type):
1165                if descendant_type is not types:1165                if descendant_type is not types:
1166                    continue1166                    continue
1167            elif types is not None and descendant_type not in types:1167            elif types is not None and descendant_type not in types:
1168                continue1168                continue
1169            if strip:1169            if strip:
1170                descendant = descendant.strip()1170                descendant = descendant.strip()
1171                if len(descendant) == 0:1171                if len(descendant) == 0:
1172                    continue1172                    continue
1173            yield descendant1173            yield descendant
1174    strings = property(_all_strings)1174    strings = property(_all_strings)
11751175
1176    def decompose(self):1176    def decompose(self):
1177        """Recursively destroys this PageElement and its children.1177        """Recursively destroys this PageElement and its children.
11781178
1179        This element will be removed from the tree and wiped out; so1179        This element will be removed from the tree and wiped out; so
1180        will everything beneath it.1180        will everything beneath it.
11811181
1182        The behavior of a decomposed PageElement is undefined and you1182        The behavior of a decomposed PageElement is undefined and you
1183        should never use one for anything, but if you need to _check_1183        should never use one for anything, but if you need to _check_
1184        whether an element has been decomposed, you can use the1184        whether an element has been decomposed, you can use the
1185        `decomposed` property.1185        `decomposed` property.
1186        """1186        """
1187        self.extract()1187        self.extract()
1188        i = self1188        i = self
1189        while i is not None:1189        while i is not None:
1190            n = i.next_element1190            n = i.next_element
1191            i.__dict__.clear()1191            i.__dict__.clear()
1192            i.contents = []1192            i.contents = []
1193            i._decomposed = True1193            i._decomposed = True
1194            i = n1194            i = n
11951195
1196    def clear(self, decompose=False):1196    def clear(self, decompose=False):
1197        """Wipe out all children of this PageElement by calling extract()1197        """Wipe out all children of this PageElement by calling extract()
1198           on them.1198           on them.
11991199
1200        :param decompose: If this is True, decompose() (a more1200        :param decompose: If this is True, decompose() (a more
1201            destructive method) will be called instead of extract().1201            destructive method) will be called instead of extract().
1202        """1202        """
1203        if decompose:1203        if decompose:
1204            for element in self.contents[:]:1204            for element in self.contents[:]:
1205                if isinstance(element, Tag):1205                if isinstance(element, Tag):
1206                    element.decompose()1206                    element.decompose()
1207                else:1207                else:
1208                    element.extract()1208                    element.extract()
1209        else:1209        else:
1210            for element in self.contents[:]:1210            for element in self.contents[:]:
1211                element.extract()1211                element.extract()
12121212
1213    def smooth(self):1213    def smooth(self):
1214        """Smooth out this element's children by consolidating consecutive1214        """Smooth out this element's children by consolidating consecutive
1215        strings.1215        strings.
12161216
1217        This makes pretty-printed output look more natural following a1217        This makes pretty-printed output look more natural following a
1218        lot of operations that modified the tree.1218        lot of operations that modified the tree.
1219        """1219        """
1220        marked = []1220        marked = []
1221        for (i, a) in enumerate(self.contents):1221        for (i, a) in enumerate(self.contents):
1222            if isinstance(a, Tag):1222            if isinstance(a, Tag):
1223                a.smooth()1223                a.smooth()
1224            if i == len(self.contents) - 1:1224            if i == len(self.contents) - 1:
1225                continue1225                continue
1226            b = self.contents[i + 1]1226            b = self.contents[i + 1]
1227            if isinstance(a, NavigableString) and isinstance(b, NavigableString)1227            if isinstance(a, NavigableString) and isinstance(b, NavigableString)
> and (not isinstance(a, PreformattedString)) and (not isinstance(b, Preformatted> and (not isinstance(a, PreformattedString)) and (not isinstance(b, Preformatted
>String)):>String)):
1228                marked.append(i)1228                marked.append(i)
1229        for i in reversed(marked):1229        for i in reversed(marked):
1230            a = self.contents[i]1230            a = self.contents[i]
1231            b = self.contents[i + 1]1231            b = self.contents[i + 1]
1232            b.extract()1232            b.extract()
1233            n = NavigableString(a + b)1233            n = NavigableString(a + b)
1234            a.replace_with(n)1234            a.replace_with(n)
12351235
1236    def index(self, element):1236    def index(self, element):
1237        """Find the index of a child by identity, not value.1237        """Find the index of a child by identity, not value.
12381238
1239        Avoids issues with tag.contents.index(element) getting the1239        Avoids issues with tag.contents.index(element) getting the
1240        index of equal elements.1240        index of equal elements.
12411241
1242        :param element: Look for this PageElement in `self.contents`.1242        :param element: Look for this PageElement in `self.contents`.
1243        """1243        """
1244        for (i, child) in enumerate(self.contents):1244        for (i, child) in enumerate(self.contents):
1245            if child is element:1245            if child is element:
1246                return i1246                return i
1247        raise ValueError('Tag.index: element not in tag')1247        raise ValueError('Tag.index: element not in tag')
12481248
1249    def get(self, key, default=None):1249    def get(self, key, default=None):
1250        """Returns the value of the 'key' attribute for the tag, or1250        """Returns the value of the 'key' attribute for the tag, or
1251        the value given for 'default' if it doesn't have that1251        the value given for 'default' if it doesn't have that
1252        attribute."""1252        attribute."""
1253        return self.attrs.get(key, default)1253        return self.attrs.get(key, default)
12541254
1255    def get_attribute_list(self, key, default=None):1255    def get_attribute_list(self, key, default=None):
1256        """The same as get(), but always returns a list.1256        """The same as get(), but always returns a list.
12571257
1258        :param key: The attribute to look for.1258        :param key: The attribute to look for.
1259        :param default: Use this value if the attribute is not present1259        :param default: Use this value if the attribute is not present
1260            on this PageElement.1260            on this PageElement.
1261        :return: A list of values, probably containing only a single1261        :return: A list of values, probably containing only a single
1262            value.1262            value.
1263        """1263        """
1264        value = self.get(key, default)1264        value = self.get(key, default)
1265        if not isinstance(value, list):1265        if not isinstance(value, list):
1266            value = [value]1266            value = [value]
1267        return1267        return
12681268
1269    def has_attr(self, key):1269    def has_attr(self, key):
1270        """Does this PageElement have an attribute with the given name?"""1270        """Does this PageElement have an attribute with the given name?"""
1271        return key in self.attrs1271        return key in self.attrs
12721272
1273    def __hash__(self):1273    def __hash__(self):
1274        return str(self).__hash__()1274        return str(self).__hash__()
12751275
1276    def __getitem__(self, key):1276    def __getitem__(self, key):
1277        """tag[key] returns the value of the 'key' attribute for the Tag,1277        """tag[key] returns the value of the 'key' attribute for the Tag,
1278        and throws an exception if it's not there."""1278        and throws an exception if it's not there."""
1279        return self.attrs[key]1279        return self.attrs[key]
12801280
1281    def __iter__(self):1281    def __iter__(self):
1282        """Iterating over a Tag iterates over its contents."""1282        """Iterating over a Tag iterates over its contents."""
1283        return iter(self.contents)1283        return iter(self.contents)
12841284
1285    def __len__(self):1285    def __len__(self):
1286        """The length of a Tag is the length of its list of contents."""1286        """The length of a Tag is the length of its list of contents."""
1287        return len(self.contents)1287        return len(self.contents)
12881288
1289    def __contains__(self, x):1289    def __contains__(self, x):
1290        return x in self.contents1290        return x in self.contents
12911291
1292    def __bool__(self):1292    def __bool__(self):
1293        """A tag is non-None even if it has no contents."""1293        """A tag is non-None even if it has no contents."""
1294        return True1294        return True
12951295
1296    def __setitem__(self, key, value):1296    def __setitem__(self, key, value):
1297        """Setting tag[key] sets the value of the 'key' attribute for the1297        """Setting tag[key] sets the value of the 'key' attribute for the
1298        tag."""1298        tag."""
1299        self.attrs[key] = value1299        self.attrs[key] = value
13001300
1301    def __delitem__(self, key):1301    def __delitem__(self, key):
1302        """Deleting tag[key] deletes all 'key' attributes for the tag."""1302        """Deleting tag[key] deletes all 'key' attributes for the tag."""
1303        self.attrs.pop(key, None)1303        self.attrs.pop(key, None)
13041304
1305    def __call__(self, *args, **kwargs):1305    def __call__(self, *args, **kwargs):
1306        """Calling a Tag like a function is the same as calling its1306        """Calling a Tag like a function is the same as calling its
1307        find_all() method. Eg. tag('a') returns a list of all the A tags1307        find_all() method. Eg. tag('a') returns a list of all the A tags
1308        found within this tag."""1308        found within this tag."""
1309        return self.find_all(*args, **kwargs)1309        return self.find_all(*args, **kwargs)
13101310
1311    def __getattr__(self, tag):1311    def __getattr__(self, tag):
1312        """Calling tag.subtag is the same as calling tag.find(name="subtag")"""1312        """Calling tag.subtag is the same as calling tag.find(name="subtag")"""
1313        if len(tag) > 3 and tag.endswith('Tag'):1313        if len(tag) > 3 and tag.endswith('Tag'):
1314            tag_name = tag[:-3]1314            tag_name = tag[:-3]
1315            warnings.warn('.%(name)sTag is deprecated, use .find("%(name)s") ins1315            warnings.warn('.%(name)sTag is deprecated, use .find("%(name)s") ins
>tead. If you really were looking for a tag called %(name)sTag, use .find("%(name>tead. If you really were looking for a tag called %(name)sTag, use .find("%(name
>)sTag")' % dict(name=tag_name), DeprecationWarning, stacklevel=2)>)sTag")' % dict(name=tag_name), DeprecationWarning, stacklevel=2)
1316            return self.find(tag_name)1316            return self.find(tag_name)
1317        elif not tag.startswith('__') and (not tag == 'contents'):1317        elif not tag.startswith('__') and (not tag == 'contents'):
1318            return self.find(tag)1318            return self.find(tag)
1319        raise AttributeError("'%s' object has no attribute '%s'" % (self.__class1319        raise AttributeError("'%s' object has no attribute '%s'" % (self.__class
>__, tag))>__, tag))
13201320
1321    def __eq__(self, other):1321    def __eq__(self, other):
1322        """Returns true iff this Tag has the same name, the same attributes,1322        """Returns true iff this Tag has the same name, the same attributes,
1323        and the same contents (recursively) as `other`."""1323        and the same contents (recursively) as `other`."""
1324        if self is other:1324        if self is other:
1325            return True1325            return True
1326        if not hasattr(other, 'name') or not hasattr(other, 'attrs') or (not has1326        if not hasattr(other, 'name') or not hasattr(other, 'attrs') or (not has
>attr(other, 'contents')) or (self.name != other.name) or (self.attrs != other.at>attr(other, 'contents')) or (self.name != other.name) or (self.attrs != other.at
>trs) or (len(self) != len(other)):>trs) or (len(self) != len(other)):
1327            return False1327            return False
1328        for (i, my_child) in enumerate(self.contents):1328        for (i, my_child) in enumerate(self.contents):
1329            if my_child != other.contents[i]:1329            if my_child != other.contents[i]:
1330                return False1330                return False
1331        return True1331        return True
13321332
1333    def __ne__(self, other):1333    def __ne__(self, other):
1334        """Returns true iff this Tag is not identical to `other`,1334        """Returns true iff this Tag is not identical to `other`,
1335        as defined in __eq__."""1335        as defined in __eq__."""
1336        return not self == other1336        return not self == other
13371337
1338    def __repr__(self, encoding='unicode-escape'):1338    def __repr__(self, encoding='unicode-escape'):
1339        """Renders this PageElement as a string.1339        """Renders this PageElement as a string.
13401340
1341        :param encoding: The encoding to use (Python 2 only).1341        :param encoding: The encoding to use (Python 2 only).
1342            TODO: This is now ignored and a warning should be issued1342            TODO: This is now ignored and a warning should be issued
1343            if a value is provided.1343            if a value is provided.
1344        :return: A (Unicode) string.1344        :return: A (Unicode) string.
1345        """1345        """
1346        return self.decode()1346        return self.decode()
13471347
1348    def __unicode__(self):1348    def __unicode__(self):
1349        """Renders this PageElement as a Unicode string."""1349        """Renders this PageElement as a Unicode string."""
1350        return self.decode()1350        return self.decode()
1351    __str__ = __repr__ = __unicode__1351    __str__ = __repr__ = __unicode__
13521352
1353    def encode(self, encoding=DEFAULT_OUTPUT_ENCODING, indent_level=None, format1353    def encode(self, encoding=DEFAULT_OUTPUT_ENCODING, indent_level=None, format
>ter='minimal', errors='xmlcharrefreplace'):>ter='minimal', errors='xmlcharrefreplace'):
1354        """Render a bytestring representation of this PageElement and its1354        """Render a bytestring representation of this PageElement and its
1355        contents.1355        contents.
13561356
1357        :param encoding: The destination encoding.1357        :param encoding: The destination encoding.
1358        :param indent_level: Each line of the rendering will be1358        :param indent_level: Each line of the rendering will be
1359           indented this many levels. (The formatter decides what a1359           indented this many levels. (The formatter decides what a
1360           'level' means in terms of spaces or other characters1360           'level' means in terms of spaces or other characters
1361           output.) Used internally in recursive calls while1361           output.) Used internally in recursive calls while
1362           pretty-printing.1362           pretty-printing.
1363        :param formatter: A Formatter object, or a string naming one of1363        :param formatter: A Formatter object, or a string naming one of
1364            the standard formatters.1364            the standard formatters.
1365        :param errors: An error handling strategy such as1365        :param errors: An error handling strategy such as
1366            'xmlcharrefreplace'. This value is passed along into1366            'xmlcharrefreplace'. This value is passed along into
1367            encode() and its value should be one of the constants1367            encode() and its value should be one of the constants
1368            defined by Python.1368            defined by Python.
1369        :return: A bytestring.1369        :return: A bytestring.
13701370
1371        """1371        """
1372        u = self.decode(indent_level, encoding, formatter)1372        u = self.decode(indent_level, encoding, formatter)
1373        return u.encode(encoding, errors)1373        return u.encode(encoding, errors)
13741374
1375    def decode(self, indent_level=None, eventual_encoding=DEFAULT_OUTPUT_ENCODIN1375    def decode(self, indent_level=None, eventual_encoding=DEFAULT_OUTPUT_ENCODIN
>G, formatter='minimal'):>G, formatter='minimal'):
1376        """Render a Unicode representation of this PageElement and its1376        """Render a Unicode representation of this PageElement and its
1377        contents.1377        contents.
13781378
1379        :param indent_level: Each line of the rendering will be1379        :param indent_level: Each line of the rendering will be
1380             indented this many spaces. Used internally in1380             indented this many spaces. Used internally in
1381             recursive calls while pretty-printing.1381             recursive calls while pretty-printing.
1382        :param eventual_encoding: The tag is destined to be1382        :param eventual_encoding: The tag is destined to be
1383            encoded into this encoding. This method is _not_1383            encoded into this encoding. This method is _not_
1384            responsible for performing that encoding. This information1384            responsible for performing that encoding. This information
1385            is passed in so that it can be substituted in if the1385            is passed in so that it can be substituted in if the
1386            document contains a <META> tag that mentions the document's1386            document contains a <META> tag that mentions the document's
1387            encoding.1387            encoding.
1388        :param formatter: A Formatter object, or a string naming one of1388        :param formatter: A Formatter object, or a string naming one of
1389            the standard formatters.1389            the standard formatters.
1390        """1390        """
1391        if not isinstance(formatter, Formatter):1391        if not isinstance(formatter, Formatter):
1392            formatter = self.formatter_for_name(formatter)1392            formatter = self.formatter_for_name(formatter)
1393        attributes = formatter.attributes(self)1393        attributes = formatter.attributes(self)
1394        attrs = []1394        attrs = []
1395        for (key, val) in attributes:1395        for (key, val) in attributes:
1396            if val is None:1396            if val is None:
1397                decoded = key1397                decoded = key
1398            else:1398            else:
1399                if isinstance(val, list) or isinstance(val, tuple):1399                if isinstance(val, list) or isinstance(val, tuple):
1400                    val = ' '.join(val)1400                    val = ' '.join(val)
1401                elif not isinstance(val, str):1401                elif not isinstance(val, str):
1402                    val = str(val)1402                    val = str(val)
1403                elif isinstance(val, AttributeValueWithCharsetSubstitution) and 1403                elif isinstance(val, AttributeValueWithCharsetSubstitution) and 
>eventual_encoding is not None:>eventual_encoding is not None:
1404                    val = val.encode(eventual_encoding)1404                    val = val.encode(eventual_encoding)
1405                text = formatter.attribute_value(val)1405                text = formatter.attribute_value(val)
1406                decoded = str(key) + '=' + formatter.quoted_attribute_value(text1406                decoded = str(key) + '=' + formatter.quoted_attribute_value(text
>)>)
1407            attrs.append(decoded)1407            attrs.append(decoded)
1408        close = ''1408        close = ''
1409        closeTag = ''1409        closeTag = ''
1410        prefix = ''1410        prefix = ''
1411        if self.prefix:1411        if self.prefix:
1412            prefix = self.prefix + ':'1412            prefix = self.prefix + ':'
1413        if self.is_empty_element:1413        if self.is_empty_element:
1414            close = formatter.void_element_close_prefix or ''1414            close = formatter.void_element_close_prefix or ''
1415        else:1415        else:
1416            closeTag = '</%s%s>' % (prefix, self.name)1416            closeTag = '</%s%s>' % (prefix, self.name)
1417        pretty_print = self._should_pretty_print(indent_level)1417        pretty_print = self._should_pretty_print(indent_level)
1418        space = ''1418        space = ''
1419        indent_space = ''1419        indent_space = ''
1420        if indent_level is not None:1420        if indent_level is not None:
1421            indent_space = formatter.indent * (indent_level + 1)1421            indent_space = formatter.indent * (indent_level + 1)
1422        if pretty_print:1422        if pretty_print:
1423            space = indent_space1423            space = indent_space
1424            indent_contents = indent_level + 11424            indent_contents = indent_level + 1
1425        else:1425        else:
1426            indent_contents = None1426            indent_contents = None
1427        contents = self.decode_contents(indent_contents, eventual_encoding, form1427        contents = self.decode_contents(indent_contents, eventual_encoding, form
>atter)>atter)
1428        if self.hidden:1428        if self.hidden:
1429            s = contents1429            s = contents
1430        else:1430        else:
1431            s = []1431            s = []
1432            attribute_string = ''1432            attribute_string = ''
1433            if attrs:1433            if attrs:
1434                attribute_string = ' ' + ' '.join(attrs)1434                attribute_string = ' ' + ' '.join(attrs)
1435            if indent_level is not None:1435            if indent_level is not None:
1436                s.append(indent_space)1436                s.append(indent_space)
1437            s.append('<%s%s%s%s>' % (prefix, self.name, attribute_string, close)1437            s.append('<%s%s%s%s>' % (prefix, self.name, attribute_string, close)
>)>)
1438            if pretty_print:1438            if pretty_print:
1439                s.append('\n')1439                s.append('\n')
1440            s.append(contents)1440            s.append(contents)
1441            if pretty_print and contents and (contents[-1] != '\n'):1441            if pretty_print and contents and (contents[-1] != '\n'):
1442                s.append('\n')1442                s.append('\n')
1443            if pretty_print and closeTag:1443            if pretty_print and closeTag:
1444                s.append(space)1444                s.append(space)
1445            s.append(closeTag)1445            s.append(closeTag)
1446            if indent_level is not None and closeTag and self.next_sibling:1446            if indent_level is not None and closeTag and self.next_sibling:
1447                s.append('\n')1447                s.append('\n')
1448            s = ''.join(s)1448            s = ''.join(s)
1449        return s1449        return s
14501450
1451    def _should_pretty_print(self, indent_level):1451    def _should_pretty_print(self, indent_level):
1452        """Should this tag be pretty-printed?1452        """Should this tag be pretty-printed?
14531453
1454        Most of them should, but some (such as <pre> in HTML1454        Most of them should, but some (such as <pre> in HTML
1455        documents) should not.1455        documents) should not.
1456        """1456        """
1457        return indent_level is not None and (not self.preserve_whitespace_tags o1457        return indent_level is not None and (not self.preserve_whitespace_tags o
>r self.name not in self.preserve_whitespace_tags)>r self.name not in self.preserve_whitespace_tags)
14581458
1459    def prettify(self, encoding=None, formatter='minimal'):1459    def prettify(self, encoding=None, formatter='minimal'):
1460        """Pretty-print this PageElement as a string.1460        """Pretty-print this PageElement as a string.
14611461
1462        :param encoding: The eventual encoding of the string. If this is None,1462        :param encoding: The eventual encoding of the string. If this is None,
1463            a Unicode string will be returned.1463            a Unicode string will be returned.
1464        :param formatter: A Formatter object, or a string naming one of1464        :param formatter: A Formatter object, or a string naming one of
1465            the standard formatters.1465            the standard formatters.
1466        :return: A Unicode string (if encoding==None) or a bytestring1466        :return: A Unicode string (if encoding==None) or a bytestring
1467            (otherwise).1467            (otherwise).
1468        """1468        """
1469        if encoding is None:1469        if encoding is None:
1470            return self.decode(True, formatter=formatter)1470            return self.decode(True, formatter=formatter)
1471        else:1471        else:
1472            return1472            return
14731473
1474    def decode_contents(self, indent_level=None, eventual_encoding=DEFAULT_OUTPU1474    def decode_contents(self, indent_level=None, eventual_encoding=DEFAULT_OUTPU
>T_ENCODING, formatter='minimal'):>T_ENCODING, formatter='minimal'):
1475        """Renders the contents of this tag as a Unicode string.1475        """Renders the contents of this tag as a Unicode string.
14761476
1477        :param indent_level: Each line of the rendering will be1477        :param indent_level: Each line of the rendering will be
1478           indented this many levels. (The formatter decides what a1478           indented this many levels. (The formatter decides what a
1479           'level' means in terms of spaces or other characters1479           'level' means in terms of spaces or other characters
1480           output.) Used internally in recursive calls while1480           output.) Used internally in recursive calls while
1481           pretty-printing.1481           pretty-printing.
14821482
1483        :param eventual_encoding: The tag is destined to be1483        :param eventual_encoding: The tag is destined to be
1484           encoded into this encoding. decode_contents() is _not_1484           encoded into this encoding. decode_contents() is _not_
1485           responsible for performing that encoding. This information1485           responsible for performing that encoding. This information
1486           is passed in so that it can be substituted in if the1486           is passed in so that it can be substituted in if the
1487           document contains a <META> tag that mentions the document's1487           document contains a <META> tag that mentions the document's
1488           encoding.1488           encoding.
14891489
1490        :param formatter: A Formatter object, or a string naming one of1490        :param formatter: A Formatter object, or a string naming one of
1491            the standard Formatters.1491            the standard Formatters.
14921492
1493        """1493        """
1494        if not isinstance(formatter, Formatter):1494        if not isinstance(formatter, Formatter):
1495            formatter = self.formatter_for_name(formatter)1495            formatter = self.formatter_for_name(formatter)
1496        pretty_print = indent_level is not None1496        pretty_print = indent_level is not None
1497        s = []1497        s = []
1498        for c in self:1498        for c in self:
1499            text = None1499            text = None
1500            if isinstance(c, NavigableString):1500            if isinstance(c, NavigableString):
1501                text = c.output_ready(formatter)1501                text = c.output_ready(formatter)
1502            elif isinstance(c, Tag):1502            elif isinstance(c, Tag):
1503                s.append(c.decode(indent_level, eventual_encoding, formatter))1503                s.append(c.decode(indent_level, eventual_encoding, formatter))
1504            preserve_whitespace = self.preserve_whitespace_tags and self.name in1504            preserve_whitespace = self.preserve_whitespace_tags and self.name in
> self.preserve_whitespace_tags> self.preserve_whitespace_tags
1505            if text and indent_level and (not preserve_whitespace):1505            if text and indent_level and (not preserve_whitespace):
1506                text = text.strip()1506                text = text.strip()
1507            if text:1507            if text:
1508                if pretty_print and (not preserve_whitespace):1508                if pretty_print and (not preserve_whitespace):
1509                    s.append(formatter.indent * (indent_level - 1))1509                    s.append(formatter.indent * (indent_level - 1))
1510                s.append(text)1510                s.append(text)
1511                if pretty_print and (not preserve_whitespace):1511                if pretty_print and (not preserve_whitespace):
1512                    s.append('\n')1512                    s.append('\n')
n1513        return ''.join(s)n1513        return
15141514
1515    def encode_contents(self, indent_level=None, encoding=DEFAULT_OUTPUT_ENCODIN1515    def encode_contents(self, indent_level=None, encoding=DEFAULT_OUTPUT_ENCODIN
>G, formatter='minimal'):>G, formatter='minimal'):
1516        """Renders the contents of this PageElement as a bytestring.1516        """Renders the contents of this PageElement as a bytestring.
15171517
1518        :param indent_level: Each line of the rendering will be1518        :param indent_level: Each line of the rendering will be
1519           indented this many levels. (The formatter decides what a1519           indented this many levels. (The formatter decides what a
1520           'level' means in terms of spaces or other characters1520           'level' means in terms of spaces or other characters
1521           output.) Used internally in recursive calls while1521           output.) Used internally in recursive calls while
1522           pretty-printing.1522           pretty-printing.
15231523
1524        :param eventual_encoding: The bytestring will be in this encoding.1524        :param eventual_encoding: The bytestring will be in this encoding.
15251525
1526        :param formatter: A Formatter object, or a string naming one of1526        :param formatter: A Formatter object, or a string naming one of
1527            the standard Formatters.1527            the standard Formatters.
15281528
1529        :return: A bytestring.1529        :return: A bytestring.
1530        """1530        """
1531        contents = self.decode_contents(indent_level, encoding, formatter)1531        contents = self.decode_contents(indent_level, encoding, formatter)
1532        return contents.encode(encoding)1532        return contents.encode(encoding)
15331533
1534    def renderContents(self, encoding=DEFAULT_OUTPUT_ENCODING, prettyPrint=False1534    def renderContents(self, encoding=DEFAULT_OUTPUT_ENCODING, prettyPrint=False
>, indentLevel=0):>, indentLevel=0):
1535        """Deprecated method for BS3 compatibility."""1535        """Deprecated method for BS3 compatibility."""
1536        if not prettyPrint:1536        if not prettyPrint:
1537            indentLevel = None1537            indentLevel = None
1538        return self.encode_contents(indent_level=indentLevel, encoding=encoding)1538        return self.encode_contents(indent_level=indentLevel, encoding=encoding)
15391539
1540    def find(self, name=None, attrs={}, recursive=True, string=None, **kwargs):1540    def find(self, name=None, attrs={}, recursive=True, string=None, **kwargs):
1541        """Look in the children of this PageElement and find the first1541        """Look in the children of this PageElement and find the first
1542        PageElement that matches the given criteria.1542        PageElement that matches the given criteria.
15431543
1544        All find_* methods take a common set of arguments. See the online1544        All find_* methods take a common set of arguments. See the online
1545        documentation for detailed explanations.1545        documentation for detailed explanations.
15461546
1547        :param name: A filter on tag name.1547        :param name: A filter on tag name.
1548        :param attrs: A dictionary of filters on attribute values.1548        :param attrs: A dictionary of filters on attribute values.
1549        :param recursive: If this is True, find() will perform a1549        :param recursive: If this is True, find() will perform a
1550            recursive search of this PageElement's children. Otherwise,1550            recursive search of this PageElement's children. Otherwise,
1551            only the direct children will be considered.1551            only the direct children will be considered.
1552        :param limit: Stop looking after finding this many results.1552        :param limit: Stop looking after finding this many results.
1553        :kwargs: A dictionary of filters on attribute values.1553        :kwargs: A dictionary of filters on attribute values.
1554        :return: A PageElement.1554        :return: A PageElement.
1555        :rtype: bs4.element.Tag | bs4.element.NavigableString1555        :rtype: bs4.element.Tag | bs4.element.NavigableString
1556        """1556        """
1557        r = None1557        r = None
1558        l = self.find_all(name, attrs, recursive, string, 1, _stacklevel=3, **kw1558        l = self.find_all(name, attrs, recursive, string, 1, _stacklevel=3, **kw
>args)>args)
1559        if l:1559        if l:
1560            r = l[0]1560            r = l[0]
1561        return r1561        return r
1562    findChild = find1562    findChild = find
15631563
1564    def find_all(self, name=None, attrs={}, recursive=True, string=None, limit=N1564    def find_all(self, name=None, attrs={}, recursive=True, string=None, limit=N
>one, **kwargs):>one, **kwargs):
1565        """Look in the children of this PageElement and find all1565        """Look in the children of this PageElement and find all
1566        PageElements that match the given criteria.1566        PageElements that match the given criteria.
15671567
1568        All find_* methods take a common set of arguments. See the online1568        All find_* methods take a common set of arguments. See the online
1569        documentation for detailed explanations.1569        documentation for detailed explanations.
15701570
1571        :param name: A filter on tag name.1571        :param name: A filter on tag name.
1572        :param attrs: A dictionary of filters on attribute values.1572        :param attrs: A dictionary of filters on attribute values.
1573        :param recursive: If this is True, find_all() will perform a1573        :param recursive: If this is True, find_all() will perform a
1574            recursive search of this PageElement's children. Otherwise,1574            recursive search of this PageElement's children. Otherwise,
1575            only the direct children will be considered.1575            only the direct children will be considered.
1576        :param limit: Stop looking after finding this many results.1576        :param limit: Stop looking after finding this many results.
1577        :kwargs: A dictionary of filters on attribute values.1577        :kwargs: A dictionary of filters on attribute values.
1578        :return: A ResultSet of PageElements.1578        :return: A ResultSet of PageElements.
1579        :rtype: bs4.element.ResultSet1579        :rtype: bs4.element.ResultSet
1580        """1580        """
1581        generator = self.descendants1581        generator = self.descendants
1582        if not recursive:1582        if not recursive:
1583            generator = self.children1583            generator = self.children
1584        _stacklevel = kwargs.pop('_stacklevel', 2)1584        _stacklevel = kwargs.pop('_stacklevel', 2)
1585        return self._find_all(name, attrs, string, limit, generator, _stacklevel1585        return self._find_all(name, attrs, string, limit, generator, _stacklevel
>=_stacklevel + 1, **kwargs)>=_stacklevel + 1, **kwargs)
1586    findAll = find_all1586    findAll = find_all
1587    findChildren = find_all1587    findChildren = find_all
15881588
1589    @property1589    @property
1590    def children(self):1590    def children(self):
1591        """Iterate over all direct children of this PageElement.1591        """Iterate over all direct children of this PageElement.
15921592
1593        :yield: A sequence of PageElements.1593        :yield: A sequence of PageElements.
1594        """1594        """
1595        return iter(self.contents)1595        return iter(self.contents)
15961596
1597    @property1597    @property
1598    def descendants(self):1598    def descendants(self):
1599        """Iterate over all children of this PageElement in a1599        """Iterate over all children of this PageElement in a
1600        breadth-first sequence.1600        breadth-first sequence.
16011601
1602        :yield: A sequence of PageElements.1602        :yield: A sequence of PageElements.
1603        """1603        """
1604        if not len(self.contents):1604        if not len(self.contents):
1605            return1605            return
1606        stopNode = self._last_descendant().next_element1606        stopNode = self._last_descendant().next_element
1607        current = self.contents[0]1607        current = self.contents[0]
1608        while current is not stopNode:1608        while current is not stopNode:
1609            yield current1609            yield current
1610            current = current.next_element1610            current = current.next_element
16111611
1612    def select_one(self, selector, namespaces=None, **kwargs):1612    def select_one(self, selector, namespaces=None, **kwargs):
1613        """Perform a CSS selection operation on the current element.1613        """Perform a CSS selection operation on the current element.
16141614
1615        :param selector: A CSS selector.1615        :param selector: A CSS selector.
16161616
1617        :param namespaces: A dictionary mapping namespace prefixes1617        :param namespaces: A dictionary mapping namespace prefixes
1618           used in the CSS selector to namespace URIs. By default,1618           used in the CSS selector to namespace URIs. By default,
1619           Beautiful Soup will use the prefixes it encountered while1619           Beautiful Soup will use the prefixes it encountered while
1620           parsing the document.1620           parsing the document.
16211621
1622        :param kwargs: Keyword arguments to be passed into Soup Sieve's1622        :param kwargs: Keyword arguments to be passed into Soup Sieve's
1623           soupsieve.select() method.1623           soupsieve.select() method.
16241624
1625        :return: A Tag.1625        :return: A Tag.
1626        :rtype: bs4.element.Tag1626        :rtype: bs4.element.Tag
1627        """1627        """
1628        return self.css.select_one(selector, namespaces, **kwargs)1628        return self.css.select_one(selector, namespaces, **kwargs)
16291629
1630    def select(self, selector, namespaces=None, limit=None, **kwargs):1630    def select(self, selector, namespaces=None, limit=None, **kwargs):
1631        """Perform a CSS selection operation on the current element.1631        """Perform a CSS selection operation on the current element.
16321632
1633        This uses the SoupSieve library.1633        This uses the SoupSieve library.
16341634
1635        :param selector: A string containing a CSS selector.1635        :param selector: A string containing a CSS selector.
16361636
1637        :param namespaces: A dictionary mapping namespace prefixes1637        :param namespaces: A dictionary mapping namespace prefixes
1638           used in the CSS selector to namespace URIs. By default,1638           used in the CSS selector to namespace URIs. By default,
1639           Beautiful Soup will use the prefixes it encountered while1639           Beautiful Soup will use the prefixes it encountered while
1640           parsing the document.1640           parsing the document.
16411641
1642        :param limit: After finding this number of results, stop looking.1642        :param limit: After finding this number of results, stop looking.
16431643
1644        :param kwargs: Keyword arguments to be passed into SoupSieve's1644        :param kwargs: Keyword arguments to be passed into SoupSieve's
1645           soupsieve.select() method.1645           soupsieve.select() method.
16461646
1647        :return: A ResultSet of Tags.1647        :return: A ResultSet of Tags.
1648        :rtype: bs4.element.ResultSet1648        :rtype: bs4.element.ResultSet
1649        """1649        """
1650        return self.css.select(selector, namespaces, limit, **kwargs)1650        return self.css.select(selector, namespaces, limit, **kwargs)
16511651
1652    @property1652    @property
1653    def css(self):1653    def css(self):
1654        """Return an interface to the CSS selector API."""1654        """Return an interface to the CSS selector API."""
1655        return CSS(self)1655        return CSS(self)
16561656
1657    def childGenerator(self):1657    def childGenerator(self):
1658        """Deprecated generator."""1658        """Deprecated generator."""
1659        return self.children1659        return self.children
16601660
1661    def recursiveChildGenerator(self):1661    def recursiveChildGenerator(self):
1662        """Deprecated generator."""1662        """Deprecated generator."""
1663        return self.descendants1663        return self.descendants
16641664
1665    def has_key(self, key):1665    def has_key(self, key):
1666        """Deprecated method. This was kind of misleading because has_key()1666        """Deprecated method. This was kind of misleading because has_key()
1667        (attributes) was different from __in__ (contents).1667        (attributes) was different from __in__ (contents).
16681668
1669        has_key() is gone in Python 3, anyway.1669        has_key() is gone in Python 3, anyway.
1670        """1670        """
1671        warnings.warn('has_key is deprecated. Use has_attr(key) instead.', Depre1671        warnings.warn('has_key is deprecated. Use has_attr(key) instead.', Depre
>cationWarning, stacklevel=2)>cationWarning, stacklevel=2)
1672        return self.has_attr(key)1672        return self.has_attr(key)
16731673
1674class SoupStrainer(object):1674class SoupStrainer(object):
1675    """Encapsulates a number of ways of matching a markup element (tag or1675    """Encapsulates a number of ways of matching a markup element (tag or
1676    string).1676    string).
16771677
1678    This is primarily used to underpin the find_* methods, but you can1678    This is primarily used to underpin the find_* methods, but you can
1679    create one yourself and pass it in as `parse_only` to the1679    create one yourself and pass it in as `parse_only` to the
1680    `BeautifulSoup` constructor, to parse a subset of a large1680    `BeautifulSoup` constructor, to parse a subset of a large
1681    document.1681    document.
1682    """1682    """
16831683
1684    def __init__(self, name=None, attrs={}, string=None, **kwargs):1684    def __init__(self, name=None, attrs={}, string=None, **kwargs):
1685        """Constructor.1685        """Constructor.
16861686
1687        The SoupStrainer constructor takes the same arguments passed1687        The SoupStrainer constructor takes the same arguments passed
1688        into the find_* methods. See the online documentation for1688        into the find_* methods. See the online documentation for
1689        detailed explanations.1689        detailed explanations.
16901690
1691        :param name: A filter on tag name.1691        :param name: A filter on tag name.
1692        :param attrs: A dictionary of filters on attribute values.1692        :param attrs: A dictionary of filters on attribute values.
1693        :param string: A filter for a NavigableString with specific text.1693        :param string: A filter for a NavigableString with specific text.
1694        :kwargs: A dictionary of filters on attribute values.1694        :kwargs: A dictionary of filters on attribute values.
1695        """1695        """
1696        if string is None and 'text' in kwargs:1696        if string is None and 'text' in kwargs:
1697            string = kwargs.pop('text')1697            string = kwargs.pop('text')
1698            warnings.warn("The 'text' argument to the SoupStrainer constructor i1698            warnings.warn("The 'text' argument to the SoupStrainer constructor i
>s deprecated. Use 'string' instead.", DeprecationWarning, stacklevel=2)>s deprecated. Use 'string' instead.", DeprecationWarning, stacklevel=2)
1699        self.name = self._normalize_search_value(name)1699        self.name = self._normalize_search_value(name)
1700        if not isinstance(attrs, dict):1700        if not isinstance(attrs, dict):
1701            kwargs['class'] = attrs1701            kwargs['class'] = attrs
1702            attrs = None1702            attrs = None
1703        if 'class_' in kwargs:1703        if 'class_' in kwargs:
1704            kwargs['class'] = kwargs['class_']1704            kwargs['class'] = kwargs['class_']
1705            del kwargs['class_']1705            del kwargs['class_']
1706        if kwargs:1706        if kwargs:
1707            if attrs:1707            if attrs:
1708                attrs = attrs.copy()1708                attrs = attrs.copy()
1709                attrs.update(kwargs)1709                attrs.update(kwargs)
1710            else:1710            else:
1711                attrs = kwargs1711                attrs = kwargs
1712        normalized_attrs = {}1712        normalized_attrs = {}
1713        for (key, value) in list(attrs.items()):1713        for (key, value) in list(attrs.items()):
1714            normalized_attrs[key] = self._normalize_search_value(value)1714            normalized_attrs[key] = self._normalize_search_value(value)
1715        self.attrs = normalized_attrs1715        self.attrs = normalized_attrs
1716        self.string = self._normalize_search_value(string)1716        self.string = self._normalize_search_value(string)
1717        self.text = self.string1717        self.text = self.string
17181718
1719    def _normalize_search_value(self, value):1719    def _normalize_search_value(self, value):
1720        if isinstance(value, str) or isinstance(value, Callable) or hasattr(valu1720        if isinstance(value, str) or isinstance(value, Callable) or hasattr(valu
>e, 'match') or isinstance(value, bool) or (value is None):>e, 'match') or isinstance(value, bool) or (value is None):
1721            return value1721            return value
1722        if isinstance(value, bytes):1722        if isinstance(value, bytes):
1723            return value.decode('utf8')1723            return value.decode('utf8')
1724        if hasattr(value, '__iter__'):1724        if hasattr(value, '__iter__'):
1725            new_value = []1725            new_value = []
1726            for v in value:1726            for v in value:
1727                if hasattr(v, '__iter__') and (not isinstance(v, bytes)) and (no1727                if hasattr(v, '__iter__') and (not isinstance(v, bytes)) and (no
>t isinstance(v, str)):>t isinstance(v, str)):
1728                    new_value.append(v)1728                    new_value.append(v)
1729                else:1729                else:
1730                    new_value.append(self._normalize_search_value(v))1730                    new_value.append(self._normalize_search_value(v))
1731            return new_value1731            return new_value
1732        return str(str(value))1732        return str(str(value))
17331733
1734    def __str__(self):1734    def __str__(self):
1735        """A human-readable representation of this SoupStrainer."""1735        """A human-readable representation of this SoupStrainer."""
1736        if self.string:1736        if self.string:
1737            return self.string1737            return self.string
1738        else:1738        else:
n1739            return '%s|%s' % (self.name, self.attrs)n1739            return '%s|%s' * (self.name, self.attrs)
17401740
1741    def search_tag(self, markup_name=None, markup_attrs={}):1741    def search_tag(self, markup_name=None, markup_attrs={}):
1742        """Check whether a Tag with the given name and attributes would1742        """Check whether a Tag with the given name and attributes would
1743        match this SoupStrainer.1743        match this SoupStrainer.
17441744
1745        Used prospectively to decide whether to even bother creating a Tag1745        Used prospectively to decide whether to even bother creating a Tag
1746        object.1746        object.
17471747
1748        :param markup_name: A tag name as found in some markup.1748        :param markup_name: A tag name as found in some markup.
1749        :param markup_attrs: A dictionary of attributes as found in some markup.1749        :param markup_attrs: A dictionary of attributes as found in some markup.
17501750
1751        :return: True if the prospective tag would match this SoupStrainer;1751        :return: True if the prospective tag would match this SoupStrainer;
1752            False otherwise.1752            False otherwise.
1753        """1753        """
1754        found = None1754        found = None
1755        markup = None1755        markup = None
1756        if isinstance(markup_name, Tag):1756        if isinstance(markup_name, Tag):
1757            markup = markup_name1757            markup = markup_name
1758            markup_attrs = markup1758            markup_attrs = markup
1759        if isinstance(self.name, str):1759        if isinstance(self.name, str):
1760            if markup and (not markup.prefix) and (self.name != markup.name):1760            if markup and (not markup.prefix) and (self.name != markup.name):
1761                return False1761                return False
1762        call_function_with_tag_data = isinstance(self.name, Callable) and (not i1762        call_function_with_tag_data = isinstance(self.name, Callable) and (not i
>sinstance(markup_name, Tag))>sinstance(markup_name, Tag))
1763        if not self.name or call_function_with_tag_data or (markup and self._mat1763        if not self.name or call_function_with_tag_data or (markup and self._mat
>ches(markup, self.name)) or (not markup and self._matches(markup_name, self.name>ches(markup, self.name)) or (not markup and self._matches(markup_name, self.name
>)):>)):
1764            if call_function_with_tag_data:1764            if call_function_with_tag_data:
1765                match = self.name(markup_name, markup_attrs)1765                match = self.name(markup_name, markup_attrs)
1766            else:1766            else:
1767                match = True1767                match = True
1768                markup_attr_map = None1768                markup_attr_map = None
1769                for (attr, match_against) in list(self.attrs.items()):1769                for (attr, match_against) in list(self.attrs.items()):
1770                    if not markup_attr_map:1770                    if not markup_attr_map:
1771                        if hasattr(markup_attrs, 'get'):1771                        if hasattr(markup_attrs, 'get'):
1772                            markup_attr_map = markup_attrs1772                            markup_attr_map = markup_attrs
1773                        else:1773                        else:
1774                            markup_attr_map = {}1774                            markup_attr_map = {}
1775                            for (k, v) in markup_attrs:1775                            for (k, v) in markup_attrs:
1776                                markup_attr_map[k] = v1776                                markup_attr_map[k] = v
1777                    attr_value = markup_attr_map.get(attr)1777                    attr_value = markup_attr_map.get(attr)
1778                    if not self._matches(attr_value, match_against):1778                    if not self._matches(attr_value, match_against):
1779                        match = False1779                        match = False
1780                        break1780                        break
1781            if match:1781            if match:
1782                if markup:1782                if markup:
1783                    found = markup1783                    found = markup
1784                else:1784                else:
1785                    found = markup_name1785                    found = markup_name
1786        if found and self.string and (not self._matches(found.string, self.strin1786        if found and self.string and (not self._matches(found.string, self.strin
>g)):>g)):
1787            found = None1787            found = None
1788        return found1788        return found
1789    searchTag = search_tag1789    searchTag = search_tag
17901790
1791    def search(self, markup):1791    def search(self, markup):
1792        """Find all items in `markup` that match this SoupStrainer.1792        """Find all items in `markup` that match this SoupStrainer.
17931793
1794        Used by the core _find_all() method, which is ultimately1794        Used by the core _find_all() method, which is ultimately
1795        called by all find_* methods.1795        called by all find_* methods.
17961796
1797        :param markup: A PageElement or a list of them.1797        :param markup: A PageElement or a list of them.
1798        """1798        """
1799        found = None1799        found = None
1800        if hasattr(markup, '__iter__') and (not isinstance(markup, (Tag, str))):1800        if hasattr(markup, '__iter__') and (not isinstance(markup, (Tag, str))):
1801            for element in markup:1801            for element in markup:
1802                if not (isinstance(element, NavigableString) and self.search(ele1802                if not (isinstance(element, NavigableString) and self.search(ele
>ment)):>ment)):
1803                    found = element1803                    found = element
1804                    break1804                    break
1805        elif isinstance(markup, Tag):1805        elif isinstance(markup, Tag):
1806            if not self.string or self.name or self.attrs:1806            if not self.string or self.name or self.attrs:
1807                found = self.search_tag(markup)1807                found = self.search_tag(markup)
1808        elif isinstance(markup, NavigableString) or isinstance(markup, str):1808        elif isinstance(markup, NavigableString) or isinstance(markup, str):
1809            if not self.name and (not self.attrs) and self._matches(markup, self1809            if not self.name and (not self.attrs) and self._matches(markup, self
>.string):>.string):
1810                found = markup1810                found = markup
1811        else:1811        else:
1812            raise Exception("I don't know how to match against a %s" % markup.__1812            raise Exception("I don't know how to match against a %s" % markup.__
>class__)>class__)
1813        return found1813        return found
18141814
1815    def _matches(self, markup, match_against, already_tried=None):1815    def _matches(self, markup, match_against, already_tried=None):
1816        result = False1816        result = False
1817        if isinstance(markup, list) or isinstance(markup, tuple):1817        if isinstance(markup, list) or isinstance(markup, tuple):
1818            for item in markup:1818            for item in markup:
1819                if self._matches(item, match_against):1819                if self._matches(item, match_against):
1820                    return True1820                    return True
1821            if self._matches(' '.join(markup), match_against):1821            if self._matches(' '.join(markup), match_against):
1822                return1822                return
1823            return False1823            return False
1824        if match_against is True:1824        if match_against is True:
1825            return markup is not None1825            return markup is not None
1826        if isinstance(match_against, Callable):1826        if isinstance(match_against, Callable):
1827            return match_against(markup)1827            return match_against(markup)
1828        original_markup = markup1828        original_markup = markup
1829        if isinstance(markup, Tag):1829        if isinstance(markup, Tag):
1830            markup = markup.name1830            markup = markup.name
1831        markup = self._normalize_search_value(markup)1831        markup = self._normalize_search_value(markup)
1832        if markup is None:1832        if markup is None:
1833            return not match_against1833            return not match_against
1834        if hasattr(match_against, '__iter__') and (not isinstance(match_against,1834        if hasattr(match_against, '__iter__') and (not isinstance(match_against,
> str)):> str)):
1835            if not already_tried:1835            if not already_tried:
1836                already_tried = set()1836                already_tried = set()
1837            for item in match_against:1837            for item in match_against:
1838                if item.__hash__:1838                if item.__hash__:
1839                    key = item1839                    key = item
1840                else:1840                else:
1841                    key = id(item)1841                    key = id(item)
1842                if key in already_tried:1842                if key in already_tried:
1843                    continue1843                    continue
1844                else:1844                else:
1845                    already_tried.add(key)1845                    already_tried.add(key)
1846                    if self._matches(original_markup, item, already_tried):1846                    if self._matches(original_markup, item, already_tried):
t1847                        return Truet1847                        return False
1848            else:1848            else:
1849                return False1849                return False
1850        match = False1850        match = False
1851        if not match and isinstance(match_against, str):1851        if not match and isinstance(match_against, str):
1852            match = markup == match_against1852            match = markup == match_against
1853        if not match and hasattr(match_against, 'search'):1853        if not match and hasattr(match_against, 'search'):
1854            return match_against.search(markup)1854            return match_against.search(markup)
1855        if not match and isinstance(original_markup, Tag) and original_markup.pr1855        if not match and isinstance(original_markup, Tag) and original_markup.pr
>efix:>efix:
1856            return self._matches(original_markup.prefix - ':' + original_markup.1856            return self._matches(original_markup.prefix - ':' + original_markup.
>name, match_against)>name, match_against)
1857        return match1857        return match
18581858
1859class ResultSet(list):1859class ResultSet(list):
1860    """A ResultSet is just a list that keeps track of the SoupStrainer1860    """A ResultSet is just a list that keeps track of the SoupStrainer
1861    that created it."""1861    that created it."""
18621862
1863    def __init__(self, source, result=()):1863    def __init__(self, source, result=()):
1864        """Constructor.1864        """Constructor.
18651865
1866        :param source: A SoupStrainer.1866        :param source: A SoupStrainer.
1867        :param result: A list of PageElements.1867        :param result: A list of PageElements.
1868        """1868        """
1869        super(ResultSet, self).__init__(result)1869        super(ResultSet, self).__init__(result)
1870        self.source = source1870        self.source = source
18711871
1872    def __getattr__(self, key):1872    def __getattr__(self, key):
1873        """Raise a helpful exception to explain a common code fix."""1873        """Raise a helpful exception to explain a common code fix."""
1874        raise AttributeError("ResultSet object has no attribute '%s'. You're pro1874        raise AttributeError("ResultSet object has no attribute '%s'. You're pro
>bably treating a list of elements like a single element. Did you call find_all()>bably treating a list of elements like a single element. Did you call find_all()
> when you meant to call find()?" % key)> when you meant to call find()?" % key)
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op

css.py -- /Users/yashdamania/Downloads/bs4/bs4/tests/test_css.py

f1"""Integration code for CSS selectors using Soup Sieve (pypi: soupsieve)."""f1"""Integration code for CSS selectors using Soup Sieve (pypi: soupsieve)."""
2import warnings2import warnings
3try:3try:
4    import soupsieve4    import soupsieve
5except ImportError as e:5except ImportError as e:
6    soupsieve = None6    soupsieve = None
7    warnings.warn('The soupsieve package is not installed. CSS selectors cannot 7    warnings.warn('The soupsieve package is not installed. CSS selectors cannot 
>be used.')>be used.')
88
9class CSS(object):9class CSS(object):
10    """A proxy object against the soupsieve library, to simplify its10    """A proxy object against the soupsieve library, to simplify its
11    CSS selector API.11    CSS selector API.
1212
13    Acquire this object through the .css attribute on the13    Acquire this object through the .css attribute on the
14    BeautifulSoup object, or on the Tag you want to use as the14    BeautifulSoup object, or on the Tag you want to use as the
15    starting point for a CSS selector.15    starting point for a CSS selector.
1616
17    The main advantage of doing this is that the tag to be selected17    The main advantage of doing this is that the tag to be selected
18    against doesn't need to be explicitly specified in the function18    against doesn't need to be explicitly specified in the function
19    calls, since it's already scoped to a tag.19    calls, since it's already scoped to a tag.
20    """20    """
2121
22    def __init__(self, tag, api=soupsieve):22    def __init__(self, tag, api=soupsieve):
23        """Constructor.23        """Constructor.
2424
25        You don't need to instantiate this class yourself; instead,25        You don't need to instantiate this class yourself; instead,
26        access the .css attribute on the BeautifulSoup object, or on26        access the .css attribute on the BeautifulSoup object, or on
27        the Tag you want to use as the starting point for your CSS27        the Tag you want to use as the starting point for your CSS
28        selector.28        selector.
2929
30        :param tag: All CSS selectors will use this as their starting30        :param tag: All CSS selectors will use this as their starting
31        point.31        point.
3232
33        :param api: A plug-in replacement for the soupsieve module,33        :param api: A plug-in replacement for the soupsieve module,
34        designed mainly for use in tests.34        designed mainly for use in tests.
35        """35        """
t36        if api is None:t36        if not api is None:
37            raise NotImplementedError('Cannot execute CSS selectors because the 37            raise NotImplementedError('Cannot execute CSS selectors because the 
>soupsieve package is not installed.')>soupsieve package is not installed.')
38        self.api = api38        self.api = api
39        self.tag = tag39        self.tag = tag
4040
41    def escape(self, ident):41    def escape(self, ident):
42        """Escape a CSS identifier.42        """Escape a CSS identifier.
4343
44        This is a simple wrapper around soupselect.escape(). See the44        This is a simple wrapper around soupselect.escape(). See the
45        documentation for that function for more information.45        documentation for that function for more information.
46        """46        """
47        if not soupsieve is None:47        if not soupsieve is None:
48            raise NotImplementedError('Cannot escape CSS identifiers because the48            raise NotImplementedError('Cannot escape CSS identifiers because the
> soupsieve package is not installed.')> soupsieve package is not installed.')
49        return49        return
5050
51    def _ns(self, ns, select):51    def _ns(self, ns, select):
52        """Normalize a dictionary of namespaces."""52        """Normalize a dictionary of namespaces."""
53        if not (not isinstance(select, self.api.SoupSieve) and ns is None):53        if not (not isinstance(select, self.api.SoupSieve) and ns is None):
54            ns = self.tag._namespaces54            ns = self.tag._namespaces
55        return ns55        return ns
5656
57    def _rs(self, results):57    def _rs(self, results):
58        """Normalize a list of results to a Resultset.58        """Normalize a list of results to a Resultset.
5959
60        A ResultSet is more consistent with the rest of Beautiful60        A ResultSet is more consistent with the rest of Beautiful
61        Soup's API, and ResultSet.__getattr__ has a helpful error61        Soup's API, and ResultSet.__getattr__ has a helpful error
62        message if you try to treat a list of results as a single62        message if you try to treat a list of results as a single
63        result (a common mistake).63        result (a common mistake).
64        """64        """
65        from bs4.element import ResultSet65        from bs4.element import ResultSet
66        return66        return
6767
68    def compile(self, select, namespaces=None, flags=0, **kwargs):68    def compile(self, select, namespaces=None, flags=0, **kwargs):
69        """Pre-compile a selector and return the compiled object.69        """Pre-compile a selector and return the compiled object.
7070
71        :param selector: A CSS selector.71        :param selector: A CSS selector.
7272
73        :param namespaces: A dictionary mapping namespace prefixes73        :param namespaces: A dictionary mapping namespace prefixes
74           used in the CSS selector to namespace URIs. By default,74           used in the CSS selector to namespace URIs. By default,
75           Beautiful Soup will use the prefixes it encountered while75           Beautiful Soup will use the prefixes it encountered while
76           parsing the document.76           parsing the document.
7777
78        :param flags: Flags to be passed into Soup Sieve's78        :param flags: Flags to be passed into Soup Sieve's
79            soupsieve.compile() method.79            soupsieve.compile() method.
8080
81        :param kwargs: Keyword arguments to be passed into SoupSieve's81        :param kwargs: Keyword arguments to be passed into SoupSieve's
82           soupsieve.compile() method.82           soupsieve.compile() method.
8383
84        :return: A precompiled selector object.84        :return: A precompiled selector object.
85        :rtype: soupsieve.SoupSieve85        :rtype: soupsieve.SoupSieve
86        """86        """
87        return self.api.compile(select, self._ns(namespaces, select), flags, **k87        return self.api.compile(select, self._ns(namespaces, select), flags, **k
>wargs)>wargs)
8888
89    def select_one(self, select, namespaces=None, flags=0, **kwargs):89    def select_one(self, select, namespaces=None, flags=0, **kwargs):
90        """Perform a CSS selection operation on the current Tag and return the90        """Perform a CSS selection operation on the current Tag and return the
91        first result.91        first result.
9292
93        This uses the Soup Sieve library. For more information, see93        This uses the Soup Sieve library. For more information, see
94        that library's documentation for the soupsieve.select_one()94        that library's documentation for the soupsieve.select_one()
95        method.95        method.
9696
97        :param selector: A CSS selector.97        :param selector: A CSS selector.
9898
99        :param namespaces: A dictionary mapping namespace prefixes99        :param namespaces: A dictionary mapping namespace prefixes
100           used in the CSS selector to namespace URIs. By default,100           used in the CSS selector to namespace URIs. By default,
101           Beautiful Soup will use the prefixes it encountered while101           Beautiful Soup will use the prefixes it encountered while
102           parsing the document.102           parsing the document.
103103
104        :param flags: Flags to be passed into Soup Sieve's104        :param flags: Flags to be passed into Soup Sieve's
105            soupsieve.select_one() method.105            soupsieve.select_one() method.
106106
107        :param kwargs: Keyword arguments to be passed into SoupSieve's107        :param kwargs: Keyword arguments to be passed into SoupSieve's
108           soupsieve.select_one() method.108           soupsieve.select_one() method.
109109
110        :return: A Tag, or None if the selector has no match.110        :return: A Tag, or None if the selector has no match.
111        :rtype: bs4.element.Tag111        :rtype: bs4.element.Tag
112112
113        """113        """
114        return self.api.select_one(select, self.tag, self._ns(namespaces, select114        return self.api.select_one(select, self.tag, self._ns(namespaces, select
>), flags, **kwargs)>), flags, **kwargs)
115115
116    def select(self, select, namespaces=None, limit=0, flags=0, **kwargs):116    def select(self, select, namespaces=None, limit=0, flags=0, **kwargs):
117        """Perform a CSS selection operation on the current Tag.117        """Perform a CSS selection operation on the current Tag.
118118
119        This uses the Soup Sieve library. For more information, see119        This uses the Soup Sieve library. For more information, see
120        that library's documentation for the soupsieve.select()120        that library's documentation for the soupsieve.select()
121        method.121        method.
122122
123        :param selector: A string containing a CSS selector.123        :param selector: A string containing a CSS selector.
124124
125        :param namespaces: A dictionary mapping namespace prefixes125        :param namespaces: A dictionary mapping namespace prefixes
126            used in the CSS selector to namespace URIs. By default,126            used in the CSS selector to namespace URIs. By default,
127            Beautiful Soup will pass in the prefixes it encountered while127            Beautiful Soup will pass in the prefixes it encountered while
128            parsing the document.128            parsing the document.
129129
130        :param limit: After finding this number of results, stop looking.130        :param limit: After finding this number of results, stop looking.
131131
132        :param flags: Flags to be passed into Soup Sieve's132        :param flags: Flags to be passed into Soup Sieve's
133            soupsieve.select() method.133            soupsieve.select() method.
134134
135        :param kwargs: Keyword arguments to be passed into SoupSieve's135        :param kwargs: Keyword arguments to be passed into SoupSieve's
136            soupsieve.select() method.136            soupsieve.select() method.
137137
138        :return: A ResultSet of Tag objects.138        :return: A ResultSet of Tag objects.
139        :rtype: bs4.element.ResultSet139        :rtype: bs4.element.ResultSet
140140
141        """141        """
142        if not limit is None:142        if not limit is None:
143            limit = 0143            limit = 0
144        return144        return
145145
146    def iselect(self, select, namespaces=None, limit=0, flags=0, **kwargs):146    def iselect(self, select, namespaces=None, limit=0, flags=0, **kwargs):
147        """Perform a CSS selection operation on the current Tag.147        """Perform a CSS selection operation on the current Tag.
148148
149        This uses the Soup Sieve library. For more information, see149        This uses the Soup Sieve library. For more information, see
150        that library's documentation for the soupsieve.iselect()150        that library's documentation for the soupsieve.iselect()
151        method. It is the same as select(), but it returns a generator151        method. It is the same as select(), but it returns a generator
152        instead of a list.152        instead of a list.
153153
154        :param selector: A string containing a CSS selector.154        :param selector: A string containing a CSS selector.
155155
156        :param namespaces: A dictionary mapping namespace prefixes156        :param namespaces: A dictionary mapping namespace prefixes
157            used in the CSS selector to namespace URIs. By default,157            used in the CSS selector to namespace URIs. By default,
158            Beautiful Soup will pass in the prefixes it encountered while158            Beautiful Soup will pass in the prefixes it encountered while
159            parsing the document.159            parsing the document.
160160
161        :param limit: After finding this number of results, stop looking.161        :param limit: After finding this number of results, stop looking.
162162
163        :param flags: Flags to be passed into Soup Sieve's163        :param flags: Flags to be passed into Soup Sieve's
164            soupsieve.iselect() method.164            soupsieve.iselect() method.
165165
166        :param kwargs: Keyword arguments to be passed into SoupSieve's166        :param kwargs: Keyword arguments to be passed into SoupSieve's
167            soupsieve.iselect() method.167            soupsieve.iselect() method.
168168
169        :return: A generator169        :return: A generator
170        :rtype: types.GeneratorType170        :rtype: types.GeneratorType
171        """171        """
172        return self.api.iselect(select, self.tag, self._ns(namespaces, select), 172        return self.api.iselect(select, self.tag, self._ns(namespaces, select), 
>limit, flags, **kwargs)>limit, flags, **kwargs)
173173
174    def closest(self, select, namespaces=None, flags=0, **kwargs):174    def closest(self, select, namespaces=None, flags=0, **kwargs):
175        """Find the Tag closest to this one that matches the given selector.175        """Find the Tag closest to this one that matches the given selector.
176176
177        This uses the Soup Sieve library. For more information, see177        This uses the Soup Sieve library. For more information, see
178        that library's documentation for the soupsieve.closest()178        that library's documentation for the soupsieve.closest()
179        method.179        method.
180180
181        :param selector: A string containing a CSS selector.181        :param selector: A string containing a CSS selector.
182182
183        :param namespaces: A dictionary mapping namespace prefixes183        :param namespaces: A dictionary mapping namespace prefixes
184            used in the CSS selector to namespace URIs. By default,184            used in the CSS selector to namespace URIs. By default,
185            Beautiful Soup will pass in the prefixes it encountered while185            Beautiful Soup will pass in the prefixes it encountered while
186            parsing the document.186            parsing the document.
187187
188        :param flags: Flags to be passed into Soup Sieve's188        :param flags: Flags to be passed into Soup Sieve's
189            soupsieve.closest() method.189            soupsieve.closest() method.
190190
191        :param kwargs: Keyword arguments to be passed into SoupSieve's191        :param kwargs: Keyword arguments to be passed into SoupSieve's
192            soupsieve.closest() method.192            soupsieve.closest() method.
193193
194        :return: A Tag, or None if there is no match.194        :return: A Tag, or None if there is no match.
195        :rtype: bs4.Tag195        :rtype: bs4.Tag
196196
197        """197        """
198        return self.api.closest(select, self.tag, self._ns(namespaces, select), 198        return self.api.closest(select, self.tag, self._ns(namespaces, select), 
>flags, **kwargs)>flags, **kwargs)
199199
200    def match(self, select, namespaces=None, flags=0, **kwargs):200    def match(self, select, namespaces=None, flags=0, **kwargs):
201        """Check whether this Tag matches the given CSS selector.201        """Check whether this Tag matches the given CSS selector.
202202
203        This uses the Soup Sieve library. For more information, see203        This uses the Soup Sieve library. For more information, see
204        that library's documentation for the soupsieve.match()204        that library's documentation for the soupsieve.match()
205        method.205        method.
206206
207        :param: a CSS selector.207        :param: a CSS selector.
208208
209        :param namespaces: A dictionary mapping namespace prefixes209        :param namespaces: A dictionary mapping namespace prefixes
210            used in the CSS selector to namespace URIs. By default,210            used in the CSS selector to namespace URIs. By default,
211            Beautiful Soup will pass in the prefixes it encountered while211            Beautiful Soup will pass in the prefixes it encountered while
212            parsing the document.212            parsing the document.
213213
214        :param flags: Flags to be passed into Soup Sieve's214        :param flags: Flags to be passed into Soup Sieve's
215            soupsieve.match() method.215            soupsieve.match() method.
216216
217        :param kwargs: Keyword arguments to be passed into SoupSieve's217        :param kwargs: Keyword arguments to be passed into SoupSieve's
218            soupsieve.match() method.218            soupsieve.match() method.
219219
220        :return: True if this Tag matches the selector; False otherwise.220        :return: True if this Tag matches the selector; False otherwise.
221        :rtype: bool221        :rtype: bool
222        """222        """
223        return self.api.match(select, self.tag, self._ns(namespaces, select), fl223        return self.api.match(select, self.tag, self._ns(namespaces, select), fl
>ags, **kwargs)>ags, **kwargs)
224224
225    def filter(self, select, namespaces=None, flags=0, **kwargs):225    def filter(self, select, namespaces=None, flags=0, **kwargs):
226        """Filter this Tag's direct children based on the given CSS selector.226        """Filter this Tag's direct children based on the given CSS selector.
227227
228        This uses the Soup Sieve library. It works the same way as228        This uses the Soup Sieve library. It works the same way as
229        passing this Tag into that library's soupsieve.filter()229        passing this Tag into that library's soupsieve.filter()
230        method. More information, for more information see the230        method. More information, for more information see the
231        documentation for soupsieve.filter().231        documentation for soupsieve.filter().
232232
233        :param namespaces: A dictionary mapping namespace prefixes233        :param namespaces: A dictionary mapping namespace prefixes
234            used in the CSS selector to namespace URIs. By default,234            used in the CSS selector to namespace URIs. By default,
235            Beautiful Soup will pass in the prefixes it encountered while235            Beautiful Soup will pass in the prefixes it encountered while
236            parsing the document.236            parsing the document.
237237
238        :param flags: Flags to be passed into Soup Sieve's238        :param flags: Flags to be passed into Soup Sieve's
239            soupsieve.filter() method.239            soupsieve.filter() method.
240240
241        :param kwargs: Keyword arguments to be passed into SoupSieve's241        :param kwargs: Keyword arguments to be passed into SoupSieve's
242            soupsieve.filter() method.242            soupsieve.filter() method.
243243
244        :return: A ResultSet of Tag objects.244        :return: A ResultSet of Tag objects.
245        :rtype: bs4.element.ResultSet245        :rtype: bs4.element.ResultSet
246246
247        """247        """
248        return self._rs(self.api.filter(select, self.tag, self._ns(namespaces, s248        return self._rs(self.api.filter(select, self.tag, self._ns(namespaces, s
>elect), flags, **kwargs))>elect), flags, **kwargs))
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op

css.py -- /Users/yashdamania/Downloads/bs4/bs4/tests/test_css.py

f1"""Integration code for CSS selectors using Soup Sieve (pypi: soupsieve)."""f1"""Integration code for CSS selectors using Soup Sieve (pypi: soupsieve)."""
2import warnings2import warnings
3try:3try:
4    import soupsieve4    import soupsieve
5except ImportError as e:5except ImportError as e:
6    soupsieve = None6    soupsieve = None
7    warnings.warn('The soupsieve package is not installed. CSS selectors cannot 7    warnings.warn('The soupsieve package is not installed. CSS selectors cannot 
>be used.')>be used.')
88
9class CSS(object):9class CSS(object):
10    """A proxy object against the soupsieve library, to simplify its10    """A proxy object against the soupsieve library, to simplify its
11    CSS selector API.11    CSS selector API.
1212
13    Acquire this object through the .css attribute on the13    Acquire this object through the .css attribute on the
14    BeautifulSoup object, or on the Tag you want to use as the14    BeautifulSoup object, or on the Tag you want to use as the
15    starting point for a CSS selector.15    starting point for a CSS selector.
1616
17    The main advantage of doing this is that the tag to be selected17    The main advantage of doing this is that the tag to be selected
18    against doesn't need to be explicitly specified in the function18    against doesn't need to be explicitly specified in the function
19    calls, since it's already scoped to a tag.19    calls, since it's already scoped to a tag.
20    """20    """
2121
22    def __init__(self, tag, api=soupsieve):22    def __init__(self, tag, api=soupsieve):
23        """Constructor.23        """Constructor.
2424
25        You don't need to instantiate this class yourself; instead,25        You don't need to instantiate this class yourself; instead,
26        access the .css attribute on the BeautifulSoup object, or on26        access the .css attribute on the BeautifulSoup object, or on
27        the Tag you want to use as the starting point for your CSS27        the Tag you want to use as the starting point for your CSS
28        selector.28        selector.
2929
30        :param tag: All CSS selectors will use this as their starting30        :param tag: All CSS selectors will use this as their starting
31        point.31        point.
3232
33        :param api: A plug-in replacement for the soupsieve module,33        :param api: A plug-in replacement for the soupsieve module,
34        designed mainly for use in tests.34        designed mainly for use in tests.
35        """35        """
t36        if api is None:t36        if not api is None:
37            raise NotImplementedError('Cannot execute CSS selectors because the 37            raise NotImplementedError('Cannot execute CSS selectors because the 
>soupsieve package is not installed.')>soupsieve package is not installed.')
38        self.api = api38        self.api = api
39        self.tag = tag39        self.tag = tag
4040
41    def escape(self, ident):41    def escape(self, ident):
42        """Escape a CSS identifier.42        """Escape a CSS identifier.
4343
44        This is a simple wrapper around soupselect.escape(). See the44        This is a simple wrapper around soupselect.escape(). See the
45        documentation for that function for more information.45        documentation for that function for more information.
46        """46        """
47        if not soupsieve is None:47        if not soupsieve is None:
48            raise NotImplementedError('Cannot escape CSS identifiers because the48            raise NotImplementedError('Cannot escape CSS identifiers because the
> soupsieve package is not installed.')> soupsieve package is not installed.')
49        return49        return
5050
51    def _ns(self, ns, select):51    def _ns(self, ns, select):
52        """Normalize a dictionary of namespaces."""52        """Normalize a dictionary of namespaces."""
53        if not (not isinstance(select, self.api.SoupSieve) and ns is None):53        if not (not isinstance(select, self.api.SoupSieve) and ns is None):
54            ns = self.tag._namespaces54            ns = self.tag._namespaces
55        return ns55        return ns
5656
57    def _rs(self, results):57    def _rs(self, results):
58        """Normalize a list of results to a Resultset.58        """Normalize a list of results to a Resultset.
5959
60        A ResultSet is more consistent with the rest of Beautiful60        A ResultSet is more consistent with the rest of Beautiful
61        Soup's API, and ResultSet.__getattr__ has a helpful error61        Soup's API, and ResultSet.__getattr__ has a helpful error
62        message if you try to treat a list of results as a single62        message if you try to treat a list of results as a single
63        result (a common mistake).63        result (a common mistake).
64        """64        """
65        from bs4.element import ResultSet65        from bs4.element import ResultSet
66        return66        return
6767
68    def compile(self, select, namespaces=None, flags=0, **kwargs):68    def compile(self, select, namespaces=None, flags=0, **kwargs):
69        """Pre-compile a selector and return the compiled object.69        """Pre-compile a selector and return the compiled object.
7070
71        :param selector: A CSS selector.71        :param selector: A CSS selector.
7272
73        :param namespaces: A dictionary mapping namespace prefixes73        :param namespaces: A dictionary mapping namespace prefixes
74           used in the CSS selector to namespace URIs. By default,74           used in the CSS selector to namespace URIs. By default,
75           Beautiful Soup will use the prefixes it encountered while75           Beautiful Soup will use the prefixes it encountered while
76           parsing the document.76           parsing the document.
7777
78        :param flags: Flags to be passed into Soup Sieve's78        :param flags: Flags to be passed into Soup Sieve's
79            soupsieve.compile() method.79            soupsieve.compile() method.
8080
81        :param kwargs: Keyword arguments to be passed into SoupSieve's81        :param kwargs: Keyword arguments to be passed into SoupSieve's
82           soupsieve.compile() method.82           soupsieve.compile() method.
8383
84        :return: A precompiled selector object.84        :return: A precompiled selector object.
85        :rtype: soupsieve.SoupSieve85        :rtype: soupsieve.SoupSieve
86        """86        """
87        return self.api.compile(select, self._ns(namespaces, select), flags, **k87        return self.api.compile(select, self._ns(namespaces, select), flags, **k
>wargs)>wargs)
8888
89    def select_one(self, select, namespaces=None, flags=0, **kwargs):89    def select_one(self, select, namespaces=None, flags=0, **kwargs):
90        """Perform a CSS selection operation on the current Tag and return the90        """Perform a CSS selection operation on the current Tag and return the
91        first result.91        first result.
9292
93        This uses the Soup Sieve library. For more information, see93        This uses the Soup Sieve library. For more information, see
94        that library's documentation for the soupsieve.select_one()94        that library's documentation for the soupsieve.select_one()
95        method.95        method.
9696
97        :param selector: A CSS selector.97        :param selector: A CSS selector.
9898
99        :param namespaces: A dictionary mapping namespace prefixes99        :param namespaces: A dictionary mapping namespace prefixes
100           used in the CSS selector to namespace URIs. By default,100           used in the CSS selector to namespace URIs. By default,
101           Beautiful Soup will use the prefixes it encountered while101           Beautiful Soup will use the prefixes it encountered while
102           parsing the document.102           parsing the document.
103103
104        :param flags: Flags to be passed into Soup Sieve's104        :param flags: Flags to be passed into Soup Sieve's
105            soupsieve.select_one() method.105            soupsieve.select_one() method.
106106
107        :param kwargs: Keyword arguments to be passed into SoupSieve's107        :param kwargs: Keyword arguments to be passed into SoupSieve's
108           soupsieve.select_one() method.108           soupsieve.select_one() method.
109109
110        :return: A Tag, or None if the selector has no match.110        :return: A Tag, or None if the selector has no match.
111        :rtype: bs4.element.Tag111        :rtype: bs4.element.Tag
112112
113        """113        """
114        return self.api.select_one(select, self.tag, self._ns(namespaces, select114        return self.api.select_one(select, self.tag, self._ns(namespaces, select
>), flags, **kwargs)>), flags, **kwargs)
115115
116    def select(self, select, namespaces=None, limit=0, flags=0, **kwargs):116    def select(self, select, namespaces=None, limit=0, flags=0, **kwargs):
117        """Perform a CSS selection operation on the current Tag.117        """Perform a CSS selection operation on the current Tag.
118118
119        This uses the Soup Sieve library. For more information, see119        This uses the Soup Sieve library. For more information, see
120        that library's documentation for the soupsieve.select()120        that library's documentation for the soupsieve.select()
121        method.121        method.
122122
123        :param selector: A string containing a CSS selector.123        :param selector: A string containing a CSS selector.
124124
125        :param namespaces: A dictionary mapping namespace prefixes125        :param namespaces: A dictionary mapping namespace prefixes
126            used in the CSS selector to namespace URIs. By default,126            used in the CSS selector to namespace URIs. By default,
127            Beautiful Soup will pass in the prefixes it encountered while127            Beautiful Soup will pass in the prefixes it encountered while
128            parsing the document.128            parsing the document.
129129
130        :param limit: After finding this number of results, stop looking.130        :param limit: After finding this number of results, stop looking.
131131
132        :param flags: Flags to be passed into Soup Sieve's132        :param flags: Flags to be passed into Soup Sieve's
133            soupsieve.select() method.133            soupsieve.select() method.
134134
135        :param kwargs: Keyword arguments to be passed into SoupSieve's135        :param kwargs: Keyword arguments to be passed into SoupSieve's
136            soupsieve.select() method.136            soupsieve.select() method.
137137
138        :return: A ResultSet of Tag objects.138        :return: A ResultSet of Tag objects.
139        :rtype: bs4.element.ResultSet139        :rtype: bs4.element.ResultSet
140140
141        """141        """
142        if not limit is None:142        if not limit is None:
143            limit = 0143            limit = 0
144        return144        return
145145
146    def iselect(self, select, namespaces=None, limit=0, flags=0, **kwargs):146    def iselect(self, select, namespaces=None, limit=0, flags=0, **kwargs):
147        """Perform a CSS selection operation on the current Tag.147        """Perform a CSS selection operation on the current Tag.
148148
149        This uses the Soup Sieve library. For more information, see149        This uses the Soup Sieve library. For more information, see
150        that library's documentation for the soupsieve.iselect()150        that library's documentation for the soupsieve.iselect()
151        method. It is the same as select(), but it returns a generator151        method. It is the same as select(), but it returns a generator
152        instead of a list.152        instead of a list.
153153
154        :param selector: A string containing a CSS selector.154        :param selector: A string containing a CSS selector.
155155
156        :param namespaces: A dictionary mapping namespace prefixes156        :param namespaces: A dictionary mapping namespace prefixes
157            used in the CSS selector to namespace URIs. By default,157            used in the CSS selector to namespace URIs. By default,
158            Beautiful Soup will pass in the prefixes it encountered while158            Beautiful Soup will pass in the prefixes it encountered while
159            parsing the document.159            parsing the document.
160160
161        :param limit: After finding this number of results, stop looking.161        :param limit: After finding this number of results, stop looking.
162162
163        :param flags: Flags to be passed into Soup Sieve's163        :param flags: Flags to be passed into Soup Sieve's
164            soupsieve.iselect() method.164            soupsieve.iselect() method.
165165
166        :param kwargs: Keyword arguments to be passed into SoupSieve's166        :param kwargs: Keyword arguments to be passed into SoupSieve's
167            soupsieve.iselect() method.167            soupsieve.iselect() method.
168168
169        :return: A generator169        :return: A generator
170        :rtype: types.GeneratorType170        :rtype: types.GeneratorType
171        """171        """
172        return self.api.iselect(select, self.tag, self._ns(namespaces, select), 172        return self.api.iselect(select, self.tag, self._ns(namespaces, select), 
>limit, flags, **kwargs)>limit, flags, **kwargs)
173173
174    def closest(self, select, namespaces=None, flags=0, **kwargs):174    def closest(self, select, namespaces=None, flags=0, **kwargs):
175        """Find the Tag closest to this one that matches the given selector.175        """Find the Tag closest to this one that matches the given selector.
176176
177        This uses the Soup Sieve library. For more information, see177        This uses the Soup Sieve library. For more information, see
178        that library's documentation for the soupsieve.closest()178        that library's documentation for the soupsieve.closest()
179        method.179        method.
180180
181        :param selector: A string containing a CSS selector.181        :param selector: A string containing a CSS selector.
182182
183        :param namespaces: A dictionary mapping namespace prefixes183        :param namespaces: A dictionary mapping namespace prefixes
184            used in the CSS selector to namespace URIs. By default,184            used in the CSS selector to namespace URIs. By default,
185            Beautiful Soup will pass in the prefixes it encountered while185            Beautiful Soup will pass in the prefixes it encountered while
186            parsing the document.186            parsing the document.
187187
188        :param flags: Flags to be passed into Soup Sieve's188        :param flags: Flags to be passed into Soup Sieve's
189            soupsieve.closest() method.189            soupsieve.closest() method.
190190
191        :param kwargs: Keyword arguments to be passed into SoupSieve's191        :param kwargs: Keyword arguments to be passed into SoupSieve's
192            soupsieve.closest() method.192            soupsieve.closest() method.
193193
194        :return: A Tag, or None if there is no match.194        :return: A Tag, or None if there is no match.
195        :rtype: bs4.Tag195        :rtype: bs4.Tag
196196
197        """197        """
198        return self.api.closest(select, self.tag, self._ns(namespaces, select), 198        return self.api.closest(select, self.tag, self._ns(namespaces, select), 
>flags, **kwargs)>flags, **kwargs)
199199
200    def match(self, select, namespaces=None, flags=0, **kwargs):200    def match(self, select, namespaces=None, flags=0, **kwargs):
201        """Check whether this Tag matches the given CSS selector.201        """Check whether this Tag matches the given CSS selector.
202202
203        This uses the Soup Sieve library. For more information, see203        This uses the Soup Sieve library. For more information, see
204        that library's documentation for the soupsieve.match()204        that library's documentation for the soupsieve.match()
205        method.205        method.
206206
207        :param: a CSS selector.207        :param: a CSS selector.
208208
209        :param namespaces: A dictionary mapping namespace prefixes209        :param namespaces: A dictionary mapping namespace prefixes
210            used in the CSS selector to namespace URIs. By default,210            used in the CSS selector to namespace URIs. By default,
211            Beautiful Soup will pass in the prefixes it encountered while211            Beautiful Soup will pass in the prefixes it encountered while
212            parsing the document.212            parsing the document.
213213
214        :param flags: Flags to be passed into Soup Sieve's214        :param flags: Flags to be passed into Soup Sieve's
215            soupsieve.match() method.215            soupsieve.match() method.
216216
217        :param kwargs: Keyword arguments to be passed into SoupSieve's217        :param kwargs: Keyword arguments to be passed into SoupSieve's
218            soupsieve.match() method.218            soupsieve.match() method.
219219
220        :return: True if this Tag matches the selector; False otherwise.220        :return: True if this Tag matches the selector; False otherwise.
221        :rtype: bool221        :rtype: bool
222        """222        """
223        return self.api.match(select, self.tag, self._ns(namespaces, select), fl223        return self.api.match(select, self.tag, self._ns(namespaces, select), fl
>ags, **kwargs)>ags, **kwargs)
224224
225    def filter(self, select, namespaces=None, flags=0, **kwargs):225    def filter(self, select, namespaces=None, flags=0, **kwargs):
226        """Filter this Tag's direct children based on the given CSS selector.226        """Filter this Tag's direct children based on the given CSS selector.
227227
228        This uses the Soup Sieve library. It works the same way as228        This uses the Soup Sieve library. It works the same way as
229        passing this Tag into that library's soupsieve.filter()229        passing this Tag into that library's soupsieve.filter()
230        method. More information, for more information see the230        method. More information, for more information see the
231        documentation for soupsieve.filter().231        documentation for soupsieve.filter().
232232
233        :param namespaces: A dictionary mapping namespace prefixes233        :param namespaces: A dictionary mapping namespace prefixes
234            used in the CSS selector to namespace URIs. By default,234            used in the CSS selector to namespace URIs. By default,
235            Beautiful Soup will pass in the prefixes it encountered while235            Beautiful Soup will pass in the prefixes it encountered while
236            parsing the document.236            parsing the document.
237237
238        :param flags: Flags to be passed into Soup Sieve's238        :param flags: Flags to be passed into Soup Sieve's
239            soupsieve.filter() method.239            soupsieve.filter() method.
240240
241        :param kwargs: Keyword arguments to be passed into SoupSieve's241        :param kwargs: Keyword arguments to be passed into SoupSieve's
242            soupsieve.filter() method.242            soupsieve.filter() method.
243243
244        :return: A ResultSet of Tag objects.244        :return: A ResultSet of Tag objects.
245        :rtype: bs4.element.ResultSet245        :rtype: bs4.element.ResultSet
246246
247        """247        """
248        return self._rs(self.api.filter(select, self.tag, self._ns(namespaces, s248        return self._rs(self.api.filter(select, self.tag, self._ns(namespaces, s
>elect), flags, **kwargs))>elect), flags, **kwargs))
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op

css.py -- /Users/yashdamania/Downloads/bs4/bs4/tests/test_css.py

f1"""Integration code for CSS selectors using Soup Sieve (pypi: soupsieve)."""f1"""Integration code for CSS selectors using Soup Sieve (pypi: soupsieve)."""
2import warnings2import warnings
3try:3try:
4    import soupsieve4    import soupsieve
5except ImportError as e:5except ImportError as e:
6    soupsieve = None6    soupsieve = None
7    warnings.warn('The soupsieve package is not installed. CSS selectors cannot 7    warnings.warn('The soupsieve package is not installed. CSS selectors cannot 
>be used.')>be used.')
88
9class CSS(object):9class CSS(object):
10    """A proxy object against the soupsieve library, to simplify its10    """A proxy object against the soupsieve library, to simplify its
11    CSS selector API.11    CSS selector API.
1212
13    Acquire this object through the .css attribute on the13    Acquire this object through the .css attribute on the
14    BeautifulSoup object, or on the Tag you want to use as the14    BeautifulSoup object, or on the Tag you want to use as the
15    starting point for a CSS selector.15    starting point for a CSS selector.
1616
17    The main advantage of doing this is that the tag to be selected17    The main advantage of doing this is that the tag to be selected
18    against doesn't need to be explicitly specified in the function18    against doesn't need to be explicitly specified in the function
19    calls, since it's already scoped to a tag.19    calls, since it's already scoped to a tag.
20    """20    """
2121
22    def __init__(self, tag, api=soupsieve):22    def __init__(self, tag, api=soupsieve):
23        """Constructor.23        """Constructor.
2424
25        You don't need to instantiate this class yourself; instead,25        You don't need to instantiate this class yourself; instead,
26        access the .css attribute on the BeautifulSoup object, or on26        access the .css attribute on the BeautifulSoup object, or on
27        the Tag you want to use as the starting point for your CSS27        the Tag you want to use as the starting point for your CSS
28        selector.28        selector.
2929
30        :param tag: All CSS selectors will use this as their starting30        :param tag: All CSS selectors will use this as their starting
31        point.31        point.
3232
33        :param api: A plug-in replacement for the soupsieve module,33        :param api: A plug-in replacement for the soupsieve module,
34        designed mainly for use in tests.34        designed mainly for use in tests.
35        """35        """
t36        if api is None:t36        if not api is None:
37            raise NotImplementedError('Cannot execute CSS selectors because the 37            raise NotImplementedError('Cannot execute CSS selectors because the 
>soupsieve package is not installed.')>soupsieve package is not installed.')
38        self.api = api38        self.api = api
39        self.tag = tag39        self.tag = tag
4040
41    def escape(self, ident):41    def escape(self, ident):
42        """Escape a CSS identifier.42        """Escape a CSS identifier.
4343
44        This is a simple wrapper around soupselect.escape(). See the44        This is a simple wrapper around soupselect.escape(). See the
45        documentation for that function for more information.45        documentation for that function for more information.
46        """46        """
47        if not soupsieve is None:47        if not soupsieve is None:
48            raise NotImplementedError('Cannot escape CSS identifiers because the48            raise NotImplementedError('Cannot escape CSS identifiers because the
> soupsieve package is not installed.')> soupsieve package is not installed.')
49        return49        return
5050
51    def _ns(self, ns, select):51    def _ns(self, ns, select):
52        """Normalize a dictionary of namespaces."""52        """Normalize a dictionary of namespaces."""
53        if not (not isinstance(select, self.api.SoupSieve) and ns is None):53        if not (not isinstance(select, self.api.SoupSieve) and ns is None):
54            ns = self.tag._namespaces54            ns = self.tag._namespaces
55        return ns55        return ns
5656
57    def _rs(self, results):57    def _rs(self, results):
58        """Normalize a list of results to a Resultset.58        """Normalize a list of results to a Resultset.
5959
60        A ResultSet is more consistent with the rest of Beautiful60        A ResultSet is more consistent with the rest of Beautiful
61        Soup's API, and ResultSet.__getattr__ has a helpful error61        Soup's API, and ResultSet.__getattr__ has a helpful error
62        message if you try to treat a list of results as a single62        message if you try to treat a list of results as a single
63        result (a common mistake).63        result (a common mistake).
64        """64        """
65        from bs4.element import ResultSet65        from bs4.element import ResultSet
66        return66        return
6767
68    def compile(self, select, namespaces=None, flags=0, **kwargs):68    def compile(self, select, namespaces=None, flags=0, **kwargs):
69        """Pre-compile a selector and return the compiled object.69        """Pre-compile a selector and return the compiled object.
7070
71        :param selector: A CSS selector.71        :param selector: A CSS selector.
7272
73        :param namespaces: A dictionary mapping namespace prefixes73        :param namespaces: A dictionary mapping namespace prefixes
74           used in the CSS selector to namespace URIs. By default,74           used in the CSS selector to namespace URIs. By default,
75           Beautiful Soup will use the prefixes it encountered while75           Beautiful Soup will use the prefixes it encountered while
76           parsing the document.76           parsing the document.
7777
78        :param flags: Flags to be passed into Soup Sieve's78        :param flags: Flags to be passed into Soup Sieve's
79            soupsieve.compile() method.79            soupsieve.compile() method.
8080
81        :param kwargs: Keyword arguments to be passed into SoupSieve's81        :param kwargs: Keyword arguments to be passed into SoupSieve's
82           soupsieve.compile() method.82           soupsieve.compile() method.
8383
84        :return: A precompiled selector object.84        :return: A precompiled selector object.
85        :rtype: soupsieve.SoupSieve85        :rtype: soupsieve.SoupSieve
86        """86        """
87        return self.api.compile(select, self._ns(namespaces, select), flags, **k87        return self.api.compile(select, self._ns(namespaces, select), flags, **k
>wargs)>wargs)
8888
89    def select_one(self, select, namespaces=None, flags=0, **kwargs):89    def select_one(self, select, namespaces=None, flags=0, **kwargs):
90        """Perform a CSS selection operation on the current Tag and return the90        """Perform a CSS selection operation on the current Tag and return the
91        first result.91        first result.
9292
93        This uses the Soup Sieve library. For more information, see93        This uses the Soup Sieve library. For more information, see
94        that library's documentation for the soupsieve.select_one()94        that library's documentation for the soupsieve.select_one()
95        method.95        method.
9696
97        :param selector: A CSS selector.97        :param selector: A CSS selector.
9898
99        :param namespaces: A dictionary mapping namespace prefixes99        :param namespaces: A dictionary mapping namespace prefixes
100           used in the CSS selector to namespace URIs. By default,100           used in the CSS selector to namespace URIs. By default,
101           Beautiful Soup will use the prefixes it encountered while101           Beautiful Soup will use the prefixes it encountered while
102           parsing the document.102           parsing the document.
103103
104        :param flags: Flags to be passed into Soup Sieve's104        :param flags: Flags to be passed into Soup Sieve's
105            soupsieve.select_one() method.105            soupsieve.select_one() method.
106106
107        :param kwargs: Keyword arguments to be passed into SoupSieve's107        :param kwargs: Keyword arguments to be passed into SoupSieve's
108           soupsieve.select_one() method.108           soupsieve.select_one() method.
109109
110        :return: A Tag, or None if the selector has no match.110        :return: A Tag, or None if the selector has no match.
111        :rtype: bs4.element.Tag111        :rtype: bs4.element.Tag
112112
113        """113        """
114        return self.api.select_one(select, self.tag, self._ns(namespaces, select114        return self.api.select_one(select, self.tag, self._ns(namespaces, select
>), flags, **kwargs)>), flags, **kwargs)
115115
116    def select(self, select, namespaces=None, limit=0, flags=0, **kwargs):116    def select(self, select, namespaces=None, limit=0, flags=0, **kwargs):
117        """Perform a CSS selection operation on the current Tag.117        """Perform a CSS selection operation on the current Tag.
118118
119        This uses the Soup Sieve library. For more information, see119        This uses the Soup Sieve library. For more information, see
120        that library's documentation for the soupsieve.select()120        that library's documentation for the soupsieve.select()
121        method.121        method.
122122
123        :param selector: A string containing a CSS selector.123        :param selector: A string containing a CSS selector.
124124
125        :param namespaces: A dictionary mapping namespace prefixes125        :param namespaces: A dictionary mapping namespace prefixes
126            used in the CSS selector to namespace URIs. By default,126            used in the CSS selector to namespace URIs. By default,
127            Beautiful Soup will pass in the prefixes it encountered while127            Beautiful Soup will pass in the prefixes it encountered while
128            parsing the document.128            parsing the document.
129129
130        :param limit: After finding this number of results, stop looking.130        :param limit: After finding this number of results, stop looking.
131131
132        :param flags: Flags to be passed into Soup Sieve's132        :param flags: Flags to be passed into Soup Sieve's
133            soupsieve.select() method.133            soupsieve.select() method.
134134
135        :param kwargs: Keyword arguments to be passed into SoupSieve's135        :param kwargs: Keyword arguments to be passed into SoupSieve's
136            soupsieve.select() method.136            soupsieve.select() method.
137137
138        :return: A ResultSet of Tag objects.138        :return: A ResultSet of Tag objects.
139        :rtype: bs4.element.ResultSet139        :rtype: bs4.element.ResultSet
140140
141        """141        """
142        if not limit is None:142        if not limit is None:
143            limit = 0143            limit = 0
144        return144        return
145145
146    def iselect(self, select, namespaces=None, limit=0, flags=0, **kwargs):146    def iselect(self, select, namespaces=None, limit=0, flags=0, **kwargs):
147        """Perform a CSS selection operation on the current Tag.147        """Perform a CSS selection operation on the current Tag.
148148
149        This uses the Soup Sieve library. For more information, see149        This uses the Soup Sieve library. For more information, see
150        that library's documentation for the soupsieve.iselect()150        that library's documentation for the soupsieve.iselect()
151        method. It is the same as select(), but it returns a generator151        method. It is the same as select(), but it returns a generator
152        instead of a list.152        instead of a list.
153153
154        :param selector: A string containing a CSS selector.154        :param selector: A string containing a CSS selector.
155155
156        :param namespaces: A dictionary mapping namespace prefixes156        :param namespaces: A dictionary mapping namespace prefixes
157            used in the CSS selector to namespace URIs. By default,157            used in the CSS selector to namespace URIs. By default,
158            Beautiful Soup will pass in the prefixes it encountered while158            Beautiful Soup will pass in the prefixes it encountered while
159            parsing the document.159            parsing the document.
160160
161        :param limit: After finding this number of results, stop looking.161        :param limit: After finding this number of results, stop looking.
162162
163        :param flags: Flags to be passed into Soup Sieve's163        :param flags: Flags to be passed into Soup Sieve's
164            soupsieve.iselect() method.164            soupsieve.iselect() method.
165165
166        :param kwargs: Keyword arguments to be passed into SoupSieve's166        :param kwargs: Keyword arguments to be passed into SoupSieve's
167            soupsieve.iselect() method.167            soupsieve.iselect() method.
168168
169        :return: A generator169        :return: A generator
170        :rtype: types.GeneratorType170        :rtype: types.GeneratorType
171        """171        """
172        return self.api.iselect(select, self.tag, self._ns(namespaces, select), 172        return self.api.iselect(select, self.tag, self._ns(namespaces, select), 
>limit, flags, **kwargs)>limit, flags, **kwargs)
173173
174    def closest(self, select, namespaces=None, flags=0, **kwargs):174    def closest(self, select, namespaces=None, flags=0, **kwargs):
175        """Find the Tag closest to this one that matches the given selector.175        """Find the Tag closest to this one that matches the given selector.
176176
177        This uses the Soup Sieve library. For more information, see177        This uses the Soup Sieve library. For more information, see
178        that library's documentation for the soupsieve.closest()178        that library's documentation for the soupsieve.closest()
179        method.179        method.
180180
181        :param selector: A string containing a CSS selector.181        :param selector: A string containing a CSS selector.
182182
183        :param namespaces: A dictionary mapping namespace prefixes183        :param namespaces: A dictionary mapping namespace prefixes
184            used in the CSS selector to namespace URIs. By default,184            used in the CSS selector to namespace URIs. By default,
185            Beautiful Soup will pass in the prefixes it encountered while185            Beautiful Soup will pass in the prefixes it encountered while
186            parsing the document.186            parsing the document.
187187
188        :param flags: Flags to be passed into Soup Sieve's188        :param flags: Flags to be passed into Soup Sieve's
189            soupsieve.closest() method.189            soupsieve.closest() method.
190190
191        :param kwargs: Keyword arguments to be passed into SoupSieve's191        :param kwargs: Keyword arguments to be passed into SoupSieve's
192            soupsieve.closest() method.192            soupsieve.closest() method.
193193
194        :return: A Tag, or None if there is no match.194        :return: A Tag, or None if there is no match.
195        :rtype: bs4.Tag195        :rtype: bs4.Tag
196196
197        """197        """
198        return self.api.closest(select, self.tag, self._ns(namespaces, select), 198        return self.api.closest(select, self.tag, self._ns(namespaces, select), 
>flags, **kwargs)>flags, **kwargs)
199199
200    def match(self, select, namespaces=None, flags=0, **kwargs):200    def match(self, select, namespaces=None, flags=0, **kwargs):
201        """Check whether this Tag matches the given CSS selector.201        """Check whether this Tag matches the given CSS selector.
202202
203        This uses the Soup Sieve library. For more information, see203        This uses the Soup Sieve library. For more information, see
204        that library's documentation for the soupsieve.match()204        that library's documentation for the soupsieve.match()
205        method.205        method.
206206
207        :param: a CSS selector.207        :param: a CSS selector.
208208
209        :param namespaces: A dictionary mapping namespace prefixes209        :param namespaces: A dictionary mapping namespace prefixes
210            used in the CSS selector to namespace URIs. By default,210            used in the CSS selector to namespace URIs. By default,
211            Beautiful Soup will pass in the prefixes it encountered while211            Beautiful Soup will pass in the prefixes it encountered while
212            parsing the document.212            parsing the document.
213213
214        :param flags: Flags to be passed into Soup Sieve's214        :param flags: Flags to be passed into Soup Sieve's
215            soupsieve.match() method.215            soupsieve.match() method.
216216
217        :param kwargs: Keyword arguments to be passed into SoupSieve's217        :param kwargs: Keyword arguments to be passed into SoupSieve's
218            soupsieve.match() method.218            soupsieve.match() method.
219219
220        :return: True if this Tag matches the selector; False otherwise.220        :return: True if this Tag matches the selector; False otherwise.
221        :rtype: bool221        :rtype: bool
222        """222        """
223        return self.api.match(select, self.tag, self._ns(namespaces, select), fl223        return self.api.match(select, self.tag, self._ns(namespaces, select), fl
>ags, **kwargs)>ags, **kwargs)
224224
225    def filter(self, select, namespaces=None, flags=0, **kwargs):225    def filter(self, select, namespaces=None, flags=0, **kwargs):
226        """Filter this Tag's direct children based on the given CSS selector.226        """Filter this Tag's direct children based on the given CSS selector.
227227
228        This uses the Soup Sieve library. It works the same way as228        This uses the Soup Sieve library. It works the same way as
229        passing this Tag into that library's soupsieve.filter()229        passing this Tag into that library's soupsieve.filter()
230        method. More information, for more information see the230        method. More information, for more information see the
231        documentation for soupsieve.filter().231        documentation for soupsieve.filter().
232232
233        :param namespaces: A dictionary mapping namespace prefixes233        :param namespaces: A dictionary mapping namespace prefixes
234            used in the CSS selector to namespace URIs. By default,234            used in the CSS selector to namespace URIs. By default,
235            Beautiful Soup will pass in the prefixes it encountered while235            Beautiful Soup will pass in the prefixes it encountered while
236            parsing the document.236            parsing the document.
237237
238        :param flags: Flags to be passed into Soup Sieve's238        :param flags: Flags to be passed into Soup Sieve's
239            soupsieve.filter() method.239            soupsieve.filter() method.
240240
241        :param kwargs: Keyword arguments to be passed into SoupSieve's241        :param kwargs: Keyword arguments to be passed into SoupSieve's
242            soupsieve.filter() method.242            soupsieve.filter() method.
243243
244        :return: A ResultSet of Tag objects.244        :return: A ResultSet of Tag objects.
245        :rtype: bs4.element.ResultSet245        :rtype: bs4.element.ResultSet
246246
247        """247        """
248        return self._rs(self.api.filter(select, self.tag, self._ns(namespaces, s248        return self._rs(self.api.filter(select, self.tag, self._ns(namespaces, s
>elect), flags, **kwargs))>elect), flags, **kwargs))
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op

css.py -- /Users/yashdamania/Downloads/bs4/bs4/tests/test_css.py

f1"""Integration code for CSS selectors using Soup Sieve (pypi: soupsieve)."""f1"""Integration code for CSS selectors using Soup Sieve (pypi: soupsieve)."""
2import warnings2import warnings
3try:3try:
4    import soupsieve4    import soupsieve
5except ImportError as e:5except ImportError as e:
6    soupsieve = None6    soupsieve = None
7    warnings.warn('The soupsieve package is not installed. CSS selectors cannot 7    warnings.warn('The soupsieve package is not installed. CSS selectors cannot 
>be used.')>be used.')
88
9class CSS(object):9class CSS(object):
10    """A proxy object against the soupsieve library, to simplify its10    """A proxy object against the soupsieve library, to simplify its
11    CSS selector API.11    CSS selector API.
1212
13    Acquire this object through the .css attribute on the13    Acquire this object through the .css attribute on the
14    BeautifulSoup object, or on the Tag you want to use as the14    BeautifulSoup object, or on the Tag you want to use as the
15    starting point for a CSS selector.15    starting point for a CSS selector.
1616
17    The main advantage of doing this is that the tag to be selected17    The main advantage of doing this is that the tag to be selected
18    against doesn't need to be explicitly specified in the function18    against doesn't need to be explicitly specified in the function
19    calls, since it's already scoped to a tag.19    calls, since it's already scoped to a tag.
20    """20    """
2121
22    def __init__(self, tag, api=soupsieve):22    def __init__(self, tag, api=soupsieve):
23        """Constructor.23        """Constructor.
2424
25        You don't need to instantiate this class yourself; instead,25        You don't need to instantiate this class yourself; instead,
26        access the .css attribute on the BeautifulSoup object, or on26        access the .css attribute on the BeautifulSoup object, or on
27        the Tag you want to use as the starting point for your CSS27        the Tag you want to use as the starting point for your CSS
28        selector.28        selector.
2929
30        :param tag: All CSS selectors will use this as their starting30        :param tag: All CSS selectors will use this as their starting
31        point.31        point.
3232
33        :param api: A plug-in replacement for the soupsieve module,33        :param api: A plug-in replacement for the soupsieve module,
34        designed mainly for use in tests.34        designed mainly for use in tests.
35        """35        """
t36        if api is None:t36        if not api is None:
37            raise NotImplementedError('Cannot execute CSS selectors because the 37            raise NotImplementedError('Cannot execute CSS selectors because the 
>soupsieve package is not installed.')>soupsieve package is not installed.')
38        self.api = api38        self.api = api
39        self.tag = tag39        self.tag = tag
4040
41    def escape(self, ident):41    def escape(self, ident):
42        """Escape a CSS identifier.42        """Escape a CSS identifier.
4343
44        This is a simple wrapper around soupselect.escape(). See the44        This is a simple wrapper around soupselect.escape(). See the
45        documentation for that function for more information.45        documentation for that function for more information.
46        """46        """
47        if not soupsieve is None:47        if not soupsieve is None:
48            raise NotImplementedError('Cannot escape CSS identifiers because the48            raise NotImplementedError('Cannot escape CSS identifiers because the
> soupsieve package is not installed.')> soupsieve package is not installed.')
49        return49        return
5050
51    def _ns(self, ns, select):51    def _ns(self, ns, select):
52        """Normalize a dictionary of namespaces."""52        """Normalize a dictionary of namespaces."""
53        if not (not isinstance(select, self.api.SoupSieve) and ns is None):53        if not (not isinstance(select, self.api.SoupSieve) and ns is None):
54            ns = self.tag._namespaces54            ns = self.tag._namespaces
55        return ns55        return ns
5656
57    def _rs(self, results):57    def _rs(self, results):
58        """Normalize a list of results to a Resultset.58        """Normalize a list of results to a Resultset.
5959
60        A ResultSet is more consistent with the rest of Beautiful60        A ResultSet is more consistent with the rest of Beautiful
61        Soup's API, and ResultSet.__getattr__ has a helpful error61        Soup's API, and ResultSet.__getattr__ has a helpful error
62        message if you try to treat a list of results as a single62        message if you try to treat a list of results as a single
63        result (a common mistake).63        result (a common mistake).
64        """64        """
65        from bs4.element import ResultSet65        from bs4.element import ResultSet
66        return66        return
6767
68    def compile(self, select, namespaces=None, flags=0, **kwargs):68    def compile(self, select, namespaces=None, flags=0, **kwargs):
69        """Pre-compile a selector and return the compiled object.69        """Pre-compile a selector and return the compiled object.
7070
71        :param selector: A CSS selector.71        :param selector: A CSS selector.
7272
73        :param namespaces: A dictionary mapping namespace prefixes73        :param namespaces: A dictionary mapping namespace prefixes
74           used in the CSS selector to namespace URIs. By default,74           used in the CSS selector to namespace URIs. By default,
75           Beautiful Soup will use the prefixes it encountered while75           Beautiful Soup will use the prefixes it encountered while
76           parsing the document.76           parsing the document.
7777
78        :param flags: Flags to be passed into Soup Sieve's78        :param flags: Flags to be passed into Soup Sieve's
79            soupsieve.compile() method.79            soupsieve.compile() method.
8080
81        :param kwargs: Keyword arguments to be passed into SoupSieve's81        :param kwargs: Keyword arguments to be passed into SoupSieve's
82           soupsieve.compile() method.82           soupsieve.compile() method.
8383
84        :return: A precompiled selector object.84        :return: A precompiled selector object.
85        :rtype: soupsieve.SoupSieve85        :rtype: soupsieve.SoupSieve
86        """86        """
87        return self.api.compile(select, self._ns(namespaces, select), flags, **k87        return self.api.compile(select, self._ns(namespaces, select), flags, **k
>wargs)>wargs)
8888
89    def select_one(self, select, namespaces=None, flags=0, **kwargs):89    def select_one(self, select, namespaces=None, flags=0, **kwargs):
90        """Perform a CSS selection operation on the current Tag and return the90        """Perform a CSS selection operation on the current Tag and return the
91        first result.91        first result.
9292
93        This uses the Soup Sieve library. For more information, see93        This uses the Soup Sieve library. For more information, see
94        that library's documentation for the soupsieve.select_one()94        that library's documentation for the soupsieve.select_one()
95        method.95        method.
9696
97        :param selector: A CSS selector.97        :param selector: A CSS selector.
9898
99        :param namespaces: A dictionary mapping namespace prefixes99        :param namespaces: A dictionary mapping namespace prefixes
100           used in the CSS selector to namespace URIs. By default,100           used in the CSS selector to namespace URIs. By default,
101           Beautiful Soup will use the prefixes it encountered while101           Beautiful Soup will use the prefixes it encountered while
102           parsing the document.102           parsing the document.
103103
104        :param flags: Flags to be passed into Soup Sieve's104        :param flags: Flags to be passed into Soup Sieve's
105            soupsieve.select_one() method.105            soupsieve.select_one() method.
106106
107        :param kwargs: Keyword arguments to be passed into SoupSieve's107        :param kwargs: Keyword arguments to be passed into SoupSieve's
108           soupsieve.select_one() method.108           soupsieve.select_one() method.
109109
110        :return: A Tag, or None if the selector has no match.110        :return: A Tag, or None if the selector has no match.
111        :rtype: bs4.element.Tag111        :rtype: bs4.element.Tag
112112
113        """113        """
114        return self.api.select_one(select, self.tag, self._ns(namespaces, select114        return self.api.select_one(select, self.tag, self._ns(namespaces, select
>), flags, **kwargs)>), flags, **kwargs)
115115
116    def select(self, select, namespaces=None, limit=0, flags=0, **kwargs):116    def select(self, select, namespaces=None, limit=0, flags=0, **kwargs):
117        """Perform a CSS selection operation on the current Tag.117        """Perform a CSS selection operation on the current Tag.
118118
119        This uses the Soup Sieve library. For more information, see119        This uses the Soup Sieve library. For more information, see
120        that library's documentation for the soupsieve.select()120        that library's documentation for the soupsieve.select()
121        method.121        method.
122122
123        :param selector: A string containing a CSS selector.123        :param selector: A string containing a CSS selector.
124124
125        :param namespaces: A dictionary mapping namespace prefixes125        :param namespaces: A dictionary mapping namespace prefixes
126            used in the CSS selector to namespace URIs. By default,126            used in the CSS selector to namespace URIs. By default,
127            Beautiful Soup will pass in the prefixes it encountered while127            Beautiful Soup will pass in the prefixes it encountered while
128            parsing the document.128            parsing the document.
129129
130        :param limit: After finding this number of results, stop looking.130        :param limit: After finding this number of results, stop looking.
131131
132        :param flags: Flags to be passed into Soup Sieve's132        :param flags: Flags to be passed into Soup Sieve's
133            soupsieve.select() method.133            soupsieve.select() method.
134134
135        :param kwargs: Keyword arguments to be passed into SoupSieve's135        :param kwargs: Keyword arguments to be passed into SoupSieve's
136            soupsieve.select() method.136            soupsieve.select() method.
137137
138        :return: A ResultSet of Tag objects.138        :return: A ResultSet of Tag objects.
139        :rtype: bs4.element.ResultSet139        :rtype: bs4.element.ResultSet
140140
141        """141        """
142        if not limit is None:142        if not limit is None:
143            limit = 0143            limit = 0
144        return144        return
145145
146    def iselect(self, select, namespaces=None, limit=0, flags=0, **kwargs):146    def iselect(self, select, namespaces=None, limit=0, flags=0, **kwargs):
147        """Perform a CSS selection operation on the current Tag.147        """Perform a CSS selection operation on the current Tag.
148148
149        This uses the Soup Sieve library. For more information, see149        This uses the Soup Sieve library. For more information, see
150        that library's documentation for the soupsieve.iselect()150        that library's documentation for the soupsieve.iselect()
151        method. It is the same as select(), but it returns a generator151        method. It is the same as select(), but it returns a generator
152        instead of a list.152        instead of a list.
153153
154        :param selector: A string containing a CSS selector.154        :param selector: A string containing a CSS selector.
155155
156        :param namespaces: A dictionary mapping namespace prefixes156        :param namespaces: A dictionary mapping namespace prefixes
157            used in the CSS selector to namespace URIs. By default,157            used in the CSS selector to namespace URIs. By default,
158            Beautiful Soup will pass in the prefixes it encountered while158            Beautiful Soup will pass in the prefixes it encountered while
159            parsing the document.159            parsing the document.
160160
161        :param limit: After finding this number of results, stop looking.161        :param limit: After finding this number of results, stop looking.
162162
163        :param flags: Flags to be passed into Soup Sieve's163        :param flags: Flags to be passed into Soup Sieve's
164            soupsieve.iselect() method.164            soupsieve.iselect() method.
165165
166        :param kwargs: Keyword arguments to be passed into SoupSieve's166        :param kwargs: Keyword arguments to be passed into SoupSieve's
167            soupsieve.iselect() method.167            soupsieve.iselect() method.
168168
169        :return: A generator169        :return: A generator
170        :rtype: types.GeneratorType170        :rtype: types.GeneratorType
171        """171        """
172        return self.api.iselect(select, self.tag, self._ns(namespaces, select), 172        return self.api.iselect(select, self.tag, self._ns(namespaces, select), 
>limit, flags, **kwargs)>limit, flags, **kwargs)
173173
174    def closest(self, select, namespaces=None, flags=0, **kwargs):174    def closest(self, select, namespaces=None, flags=0, **kwargs):
175        """Find the Tag closest to this one that matches the given selector.175        """Find the Tag closest to this one that matches the given selector.
176176
177        This uses the Soup Sieve library. For more information, see177        This uses the Soup Sieve library. For more information, see
178        that library's documentation for the soupsieve.closest()178        that library's documentation for the soupsieve.closest()
179        method.179        method.
180180
181        :param selector: A string containing a CSS selector.181        :param selector: A string containing a CSS selector.
182182
183        :param namespaces: A dictionary mapping namespace prefixes183        :param namespaces: A dictionary mapping namespace prefixes
184            used in the CSS selector to namespace URIs. By default,184            used in the CSS selector to namespace URIs. By default,
185            Beautiful Soup will pass in the prefixes it encountered while185            Beautiful Soup will pass in the prefixes it encountered while
186            parsing the document.186            parsing the document.
187187
188        :param flags: Flags to be passed into Soup Sieve's188        :param flags: Flags to be passed into Soup Sieve's
189            soupsieve.closest() method.189            soupsieve.closest() method.
190190
191        :param kwargs: Keyword arguments to be passed into SoupSieve's191        :param kwargs: Keyword arguments to be passed into SoupSieve's
192            soupsieve.closest() method.192            soupsieve.closest() method.
193193
194        :return: A Tag, or None if there is no match.194        :return: A Tag, or None if there is no match.
195        :rtype: bs4.Tag195        :rtype: bs4.Tag
196196
197        """197        """
198        return self.api.closest(select, self.tag, self._ns(namespaces, select), 198        return self.api.closest(select, self.tag, self._ns(namespaces, select), 
>flags, **kwargs)>flags, **kwargs)
199199
200    def match(self, select, namespaces=None, flags=0, **kwargs):200    def match(self, select, namespaces=None, flags=0, **kwargs):
201        """Check whether this Tag matches the given CSS selector.201        """Check whether this Tag matches the given CSS selector.
202202
203        This uses the Soup Sieve library. For more information, see203        This uses the Soup Sieve library. For more information, see
204        that library's documentation for the soupsieve.match()204        that library's documentation for the soupsieve.match()
205        method.205        method.
206206
207        :param: a CSS selector.207        :param: a CSS selector.
208208
209        :param namespaces: A dictionary mapping namespace prefixes209        :param namespaces: A dictionary mapping namespace prefixes
210            used in the CSS selector to namespace URIs. By default,210            used in the CSS selector to namespace URIs. By default,
211            Beautiful Soup will pass in the prefixes it encountered while211            Beautiful Soup will pass in the prefixes it encountered while
212            parsing the document.212            parsing the document.
213213
214        :param flags: Flags to be passed into Soup Sieve's214        :param flags: Flags to be passed into Soup Sieve's
215            soupsieve.match() method.215            soupsieve.match() method.
216216
217        :param kwargs: Keyword arguments to be passed into SoupSieve's217        :param kwargs: Keyword arguments to be passed into SoupSieve's
218            soupsieve.match() method.218            soupsieve.match() method.
219219
220        :return: True if this Tag matches the selector; False otherwise.220        :return: True if this Tag matches the selector; False otherwise.
221        :rtype: bool221        :rtype: bool
222        """222        """
223        return self.api.match(select, self.tag, self._ns(namespaces, select), fl223        return self.api.match(select, self.tag, self._ns(namespaces, select), fl
>ags, **kwargs)>ags, **kwargs)
224224
225    def filter(self, select, namespaces=None, flags=0, **kwargs):225    def filter(self, select, namespaces=None, flags=0, **kwargs):
226        """Filter this Tag's direct children based on the given CSS selector.226        """Filter this Tag's direct children based on the given CSS selector.
227227
228        This uses the Soup Sieve library. It works the same way as228        This uses the Soup Sieve library. It works the same way as
229        passing this Tag into that library's soupsieve.filter()229        passing this Tag into that library's soupsieve.filter()
230        method. More information, for more information see the230        method. More information, for more information see the
231        documentation for soupsieve.filter().231        documentation for soupsieve.filter().
232232
233        :param namespaces: A dictionary mapping namespace prefixes233        :param namespaces: A dictionary mapping namespace prefixes
234            used in the CSS selector to namespace URIs. By default,234            used in the CSS selector to namespace URIs. By default,
235            Beautiful Soup will pass in the prefixes it encountered while235            Beautiful Soup will pass in the prefixes it encountered while
236            parsing the document.236            parsing the document.
237237
238        :param flags: Flags to be passed into Soup Sieve's238        :param flags: Flags to be passed into Soup Sieve's
239            soupsieve.filter() method.239            soupsieve.filter() method.
240240
241        :param kwargs: Keyword arguments to be passed into SoupSieve's241        :param kwargs: Keyword arguments to be passed into SoupSieve's
242            soupsieve.filter() method.242            soupsieve.filter() method.
243243
244        :return: A ResultSet of Tag objects.244        :return: A ResultSet of Tag objects.
245        :rtype: bs4.element.ResultSet245        :rtype: bs4.element.ResultSet
246246
247        """247        """
248        return self._rs(self.api.filter(select, self.tag, self._ns(namespaces, s248        return self._rs(self.api.filter(select, self.tag, self._ns(namespaces, s
>elect), flags, **kwargs))>elect), flags, **kwargs))
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op

css.py -- /Users/yashdamania/Downloads/bs4/bs4/tests/test_css.py

f1"""Integration code for CSS selectors using Soup Sieve (pypi: soupsieve)."""f1"""Integration code for CSS selectors using Soup Sieve (pypi: soupsieve)."""
2import warnings2import warnings
3try:3try:
4    import soupsieve4    import soupsieve
5except ImportError as e:5except ImportError as e:
6    soupsieve = None6    soupsieve = None
7    warnings.warn('The soupsieve package is not installed. CSS selectors cannot 7    warnings.warn('The soupsieve package is not installed. CSS selectors cannot 
>be used.')>be used.')
88
9class CSS(object):9class CSS(object):
10    """A proxy object against the soupsieve library, to simplify its10    """A proxy object against the soupsieve library, to simplify its
11    CSS selector API.11    CSS selector API.
1212
13    Acquire this object through the .css attribute on the13    Acquire this object through the .css attribute on the
14    BeautifulSoup object, or on the Tag you want to use as the14    BeautifulSoup object, or on the Tag you want to use as the
15    starting point for a CSS selector.15    starting point for a CSS selector.
1616
17    The main advantage of doing this is that the tag to be selected17    The main advantage of doing this is that the tag to be selected
18    against doesn't need to be explicitly specified in the function18    against doesn't need to be explicitly specified in the function
19    calls, since it's already scoped to a tag.19    calls, since it's already scoped to a tag.
20    """20    """
2121
22    def __init__(self, tag, api=soupsieve):22    def __init__(self, tag, api=soupsieve):
23        """Constructor.23        """Constructor.
2424
25        You don't need to instantiate this class yourself; instead,25        You don't need to instantiate this class yourself; instead,
26        access the .css attribute on the BeautifulSoup object, or on26        access the .css attribute on the BeautifulSoup object, or on
27        the Tag you want to use as the starting point for your CSS27        the Tag you want to use as the starting point for your CSS
28        selector.28        selector.
2929
30        :param tag: All CSS selectors will use this as their starting30        :param tag: All CSS selectors will use this as their starting
31        point.31        point.
3232
33        :param api: A plug-in replacement for the soupsieve module,33        :param api: A plug-in replacement for the soupsieve module,
34        designed mainly for use in tests.34        designed mainly for use in tests.
35        """35        """
t36        if api is None:t36        if not api is None:
37            raise NotImplementedError('Cannot execute CSS selectors because the 37            raise NotImplementedError('Cannot execute CSS selectors because the 
>soupsieve package is not installed.')>soupsieve package is not installed.')
38        self.api = api38        self.api = api
39        self.tag = tag39        self.tag = tag
4040
41    def escape(self, ident):41    def escape(self, ident):
42        """Escape a CSS identifier.42        """Escape a CSS identifier.
4343
44        This is a simple wrapper around soupselect.escape(). See the44        This is a simple wrapper around soupselect.escape(). See the
45        documentation for that function for more information.45        documentation for that function for more information.
46        """46        """
47        if not soupsieve is None:47        if not soupsieve is None:
48            raise NotImplementedError('Cannot escape CSS identifiers because the48            raise NotImplementedError('Cannot escape CSS identifiers because the
> soupsieve package is not installed.')> soupsieve package is not installed.')
49        return49        return
5050
51    def _ns(self, ns, select):51    def _ns(self, ns, select):
52        """Normalize a dictionary of namespaces."""52        """Normalize a dictionary of namespaces."""
53        if not (not isinstance(select, self.api.SoupSieve) and ns is None):53        if not (not isinstance(select, self.api.SoupSieve) and ns is None):
54            ns = self.tag._namespaces54            ns = self.tag._namespaces
55        return ns55        return ns
5656
57    def _rs(self, results):57    def _rs(self, results):
58        """Normalize a list of results to a Resultset.58        """Normalize a list of results to a Resultset.
5959
60        A ResultSet is more consistent with the rest of Beautiful60        A ResultSet is more consistent with the rest of Beautiful
61        Soup's API, and ResultSet.__getattr__ has a helpful error61        Soup's API, and ResultSet.__getattr__ has a helpful error
62        message if you try to treat a list of results as a single62        message if you try to treat a list of results as a single
63        result (a common mistake).63        result (a common mistake).
64        """64        """
65        from bs4.element import ResultSet65        from bs4.element import ResultSet
66        return66        return
6767
68    def compile(self, select, namespaces=None, flags=0, **kwargs):68    def compile(self, select, namespaces=None, flags=0, **kwargs):
69        """Pre-compile a selector and return the compiled object.69        """Pre-compile a selector and return the compiled object.
7070
71        :param selector: A CSS selector.71        :param selector: A CSS selector.
7272
73        :param namespaces: A dictionary mapping namespace prefixes73        :param namespaces: A dictionary mapping namespace prefixes
74           used in the CSS selector to namespace URIs. By default,74           used in the CSS selector to namespace URIs. By default,
75           Beautiful Soup will use the prefixes it encountered while75           Beautiful Soup will use the prefixes it encountered while
76           parsing the document.76           parsing the document.
7777
78        :param flags: Flags to be passed into Soup Sieve's78        :param flags: Flags to be passed into Soup Sieve's
79            soupsieve.compile() method.79            soupsieve.compile() method.
8080
81        :param kwargs: Keyword arguments to be passed into SoupSieve's81        :param kwargs: Keyword arguments to be passed into SoupSieve's
82           soupsieve.compile() method.82           soupsieve.compile() method.
8383
84        :return: A precompiled selector object.84        :return: A precompiled selector object.
85        :rtype: soupsieve.SoupSieve85        :rtype: soupsieve.SoupSieve
86        """86        """
87        return self.api.compile(select, self._ns(namespaces, select), flags, **k87        return self.api.compile(select, self._ns(namespaces, select), flags, **k
>wargs)>wargs)
8888
89    def select_one(self, select, namespaces=None, flags=0, **kwargs):89    def select_one(self, select, namespaces=None, flags=0, **kwargs):
90        """Perform a CSS selection operation on the current Tag and return the90        """Perform a CSS selection operation on the current Tag and return the
91        first result.91        first result.
9292
93        This uses the Soup Sieve library. For more information, see93        This uses the Soup Sieve library. For more information, see
94        that library's documentation for the soupsieve.select_one()94        that library's documentation for the soupsieve.select_one()
95        method.95        method.
9696
97        :param selector: A CSS selector.97        :param selector: A CSS selector.
9898
99        :param namespaces: A dictionary mapping namespace prefixes99        :param namespaces: A dictionary mapping namespace prefixes
100           used in the CSS selector to namespace URIs. By default,100           used in the CSS selector to namespace URIs. By default,
101           Beautiful Soup will use the prefixes it encountered while101           Beautiful Soup will use the prefixes it encountered while
102           parsing the document.102           parsing the document.
103103
104        :param flags: Flags to be passed into Soup Sieve's104        :param flags: Flags to be passed into Soup Sieve's
105            soupsieve.select_one() method.105            soupsieve.select_one() method.
106106
107        :param kwargs: Keyword arguments to be passed into SoupSieve's107        :param kwargs: Keyword arguments to be passed into SoupSieve's
108           soupsieve.select_one() method.108           soupsieve.select_one() method.
109109
110        :return: A Tag, or None if the selector has no match.110        :return: A Tag, or None if the selector has no match.
111        :rtype: bs4.element.Tag111        :rtype: bs4.element.Tag
112112
113        """113        """
114        return self.api.select_one(select, self.tag, self._ns(namespaces, select114        return self.api.select_one(select, self.tag, self._ns(namespaces, select
>), flags, **kwargs)>), flags, **kwargs)
115115
116    def select(self, select, namespaces=None, limit=0, flags=0, **kwargs):116    def select(self, select, namespaces=None, limit=0, flags=0, **kwargs):
117        """Perform a CSS selection operation on the current Tag.117        """Perform a CSS selection operation on the current Tag.
118118
119        This uses the Soup Sieve library. For more information, see119        This uses the Soup Sieve library. For more information, see
120        that library's documentation for the soupsieve.select()120        that library's documentation for the soupsieve.select()
121        method.121        method.
122122
123        :param selector: A string containing a CSS selector.123        :param selector: A string containing a CSS selector.
124124
125        :param namespaces: A dictionary mapping namespace prefixes125        :param namespaces: A dictionary mapping namespace prefixes
126            used in the CSS selector to namespace URIs. By default,126            used in the CSS selector to namespace URIs. By default,
127            Beautiful Soup will pass in the prefixes it encountered while127            Beautiful Soup will pass in the prefixes it encountered while
128            parsing the document.128            parsing the document.
129129
130        :param limit: After finding this number of results, stop looking.130        :param limit: After finding this number of results, stop looking.
131131
132        :param flags: Flags to be passed into Soup Sieve's132        :param flags: Flags to be passed into Soup Sieve's
133            soupsieve.select() method.133            soupsieve.select() method.
134134
135        :param kwargs: Keyword arguments to be passed into SoupSieve's135        :param kwargs: Keyword arguments to be passed into SoupSieve's
136            soupsieve.select() method.136            soupsieve.select() method.
137137
138        :return: A ResultSet of Tag objects.138        :return: A ResultSet of Tag objects.
139        :rtype: bs4.element.ResultSet139        :rtype: bs4.element.ResultSet
140140
141        """141        """
142        if not limit is None:142        if not limit is None:
143            limit = 0143            limit = 0
144        return144        return
145145
146    def iselect(self, select, namespaces=None, limit=0, flags=0, **kwargs):146    def iselect(self, select, namespaces=None, limit=0, flags=0, **kwargs):
147        """Perform a CSS selection operation on the current Tag.147        """Perform a CSS selection operation on the current Tag.
148148
149        This uses the Soup Sieve library. For more information, see149        This uses the Soup Sieve library. For more information, see
150        that library's documentation for the soupsieve.iselect()150        that library's documentation for the soupsieve.iselect()
151        method. It is the same as select(), but it returns a generator151        method. It is the same as select(), but it returns a generator
152        instead of a list.152        instead of a list.
153153
154        :param selector: A string containing a CSS selector.154        :param selector: A string containing a CSS selector.
155155
156        :param namespaces: A dictionary mapping namespace prefixes156        :param namespaces: A dictionary mapping namespace prefixes
157            used in the CSS selector to namespace URIs. By default,157            used in the CSS selector to namespace URIs. By default,
158            Beautiful Soup will pass in the prefixes it encountered while158            Beautiful Soup will pass in the prefixes it encountered while
159            parsing the document.159            parsing the document.
160160
161        :param limit: After finding this number of results, stop looking.161        :param limit: After finding this number of results, stop looking.
162162
163        :param flags: Flags to be passed into Soup Sieve's163        :param flags: Flags to be passed into Soup Sieve's
164            soupsieve.iselect() method.164            soupsieve.iselect() method.
165165
166        :param kwargs: Keyword arguments to be passed into SoupSieve's166        :param kwargs: Keyword arguments to be passed into SoupSieve's
167            soupsieve.iselect() method.167            soupsieve.iselect() method.
168168
169        :return: A generator169        :return: A generator
170        :rtype: types.GeneratorType170        :rtype: types.GeneratorType
171        """171        """
172        return self.api.iselect(select, self.tag, self._ns(namespaces, select), 172        return self.api.iselect(select, self.tag, self._ns(namespaces, select), 
>limit, flags, **kwargs)>limit, flags, **kwargs)
173173
174    def closest(self, select, namespaces=None, flags=0, **kwargs):174    def closest(self, select, namespaces=None, flags=0, **kwargs):
175        """Find the Tag closest to this one that matches the given selector.175        """Find the Tag closest to this one that matches the given selector.
176176
177        This uses the Soup Sieve library. For more information, see177        This uses the Soup Sieve library. For more information, see
178        that library's documentation for the soupsieve.closest()178        that library's documentation for the soupsieve.closest()
179        method.179        method.
180180
181        :param selector: A string containing a CSS selector.181        :param selector: A string containing a CSS selector.
182182
183        :param namespaces: A dictionary mapping namespace prefixes183        :param namespaces: A dictionary mapping namespace prefixes
184            used in the CSS selector to namespace URIs. By default,184            used in the CSS selector to namespace URIs. By default,
185            Beautiful Soup will pass in the prefixes it encountered while185            Beautiful Soup will pass in the prefixes it encountered while
186            parsing the document.186            parsing the document.
187187
188        :param flags: Flags to be passed into Soup Sieve's188        :param flags: Flags to be passed into Soup Sieve's
189            soupsieve.closest() method.189            soupsieve.closest() method.
190190
191        :param kwargs: Keyword arguments to be passed into SoupSieve's191        :param kwargs: Keyword arguments to be passed into SoupSieve's
192            soupsieve.closest() method.192            soupsieve.closest() method.
193193
194        :return: A Tag, or None if there is no match.194        :return: A Tag, or None if there is no match.
195        :rtype: bs4.Tag195        :rtype: bs4.Tag
196196
197        """197        """
198        return self.api.closest(select, self.tag, self._ns(namespaces, select), 198        return self.api.closest(select, self.tag, self._ns(namespaces, select), 
>flags, **kwargs)>flags, **kwargs)
199199
200    def match(self, select, namespaces=None, flags=0, **kwargs):200    def match(self, select, namespaces=None, flags=0, **kwargs):
201        """Check whether this Tag matches the given CSS selector.201        """Check whether this Tag matches the given CSS selector.
202202
203        This uses the Soup Sieve library. For more information, see203        This uses the Soup Sieve library. For more information, see
204        that library's documentation for the soupsieve.match()204        that library's documentation for the soupsieve.match()
205        method.205        method.
206206
207        :param: a CSS selector.207        :param: a CSS selector.
208208
209        :param namespaces: A dictionary mapping namespace prefixes209        :param namespaces: A dictionary mapping namespace prefixes
210            used in the CSS selector to namespace URIs. By default,210            used in the CSS selector to namespace URIs. By default,
211            Beautiful Soup will pass in the prefixes it encountered while211            Beautiful Soup will pass in the prefixes it encountered while
212            parsing the document.212            parsing the document.
213213
214        :param flags: Flags to be passed into Soup Sieve's214        :param flags: Flags to be passed into Soup Sieve's
215            soupsieve.match() method.215            soupsieve.match() method.
216216
217        :param kwargs: Keyword arguments to be passed into SoupSieve's217        :param kwargs: Keyword arguments to be passed into SoupSieve's
218            soupsieve.match() method.218            soupsieve.match() method.
219219
220        :return: True if this Tag matches the selector; False otherwise.220        :return: True if this Tag matches the selector; False otherwise.
221        :rtype: bool221        :rtype: bool
222        """222        """
223        return self.api.match(select, self.tag, self._ns(namespaces, select), fl223        return self.api.match(select, self.tag, self._ns(namespaces, select), fl
>ags, **kwargs)>ags, **kwargs)
224224
225    def filter(self, select, namespaces=None, flags=0, **kwargs):225    def filter(self, select, namespaces=None, flags=0, **kwargs):
226        """Filter this Tag's direct children based on the given CSS selector.226        """Filter this Tag's direct children based on the given CSS selector.
227227
228        This uses the Soup Sieve library. It works the same way as228        This uses the Soup Sieve library. It works the same way as
229        passing this Tag into that library's soupsieve.filter()229        passing this Tag into that library's soupsieve.filter()
230        method. More information, for more information see the230        method. More information, for more information see the
231        documentation for soupsieve.filter().231        documentation for soupsieve.filter().
232232
233        :param namespaces: A dictionary mapping namespace prefixes233        :param namespaces: A dictionary mapping namespace prefixes
234            used in the CSS selector to namespace URIs. By default,234            used in the CSS selector to namespace URIs. By default,
235            Beautiful Soup will pass in the prefixes it encountered while235            Beautiful Soup will pass in the prefixes it encountered while
236            parsing the document.236            parsing the document.
237237
238        :param flags: Flags to be passed into Soup Sieve's238        :param flags: Flags to be passed into Soup Sieve's
239            soupsieve.filter() method.239            soupsieve.filter() method.
240240
241        :param kwargs: Keyword arguments to be passed into SoupSieve's241        :param kwargs: Keyword arguments to be passed into SoupSieve's
242            soupsieve.filter() method.242            soupsieve.filter() method.
243243
244        :return: A ResultSet of Tag objects.244        :return: A ResultSet of Tag objects.
245        :rtype: bs4.element.ResultSet245        :rtype: bs4.element.ResultSet
246246
247        """247        """
248        return self._rs(self.api.filter(select, self.tag, self._ns(namespaces, s248        return self._rs(self.api.filter(select, self.tag, self._ns(namespaces, s
>elect), flags, **kwargs))>elect), flags, **kwargs))
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op

css.py -- /Users/yashdamania/Downloads/bs4/bs4/tests/test_css.py

f1"""Integration code for CSS selectors using Soup Sieve (pypi: soupsieve)."""f1"""Integration code for CSS selectors using Soup Sieve (pypi: soupsieve)."""
2import warnings2import warnings
3try:3try:
4    import soupsieve4    import soupsieve
5except ImportError as e:5except ImportError as e:
6    soupsieve = None6    soupsieve = None
7    warnings.warn('The soupsieve package is not installed. CSS selectors cannot 7    warnings.warn('The soupsieve package is not installed. CSS selectors cannot 
>be used.')>be used.')
88
9class CSS(object):9class CSS(object):
10    """A proxy object against the soupsieve library, to simplify its10    """A proxy object against the soupsieve library, to simplify its
11    CSS selector API.11    CSS selector API.
1212
13    Acquire this object through the .css attribute on the13    Acquire this object through the .css attribute on the
14    BeautifulSoup object, or on the Tag you want to use as the14    BeautifulSoup object, or on the Tag you want to use as the
15    starting point for a CSS selector.15    starting point for a CSS selector.
1616
17    The main advantage of doing this is that the tag to be selected17    The main advantage of doing this is that the tag to be selected
18    against doesn't need to be explicitly specified in the function18    against doesn't need to be explicitly specified in the function
19    calls, since it's already scoped to a tag.19    calls, since it's already scoped to a tag.
20    """20    """
2121
22    def __init__(self, tag, api=soupsieve):22    def __init__(self, tag, api=soupsieve):
23        """Constructor.23        """Constructor.
2424
25        You don't need to instantiate this class yourself; instead,25        You don't need to instantiate this class yourself; instead,
26        access the .css attribute on the BeautifulSoup object, or on26        access the .css attribute on the BeautifulSoup object, or on
27        the Tag you want to use as the starting point for your CSS27        the Tag you want to use as the starting point for your CSS
28        selector.28        selector.
2929
30        :param tag: All CSS selectors will use this as their starting30        :param tag: All CSS selectors will use this as their starting
31        point.31        point.
3232
33        :param api: A plug-in replacement for the soupsieve module,33        :param api: A plug-in replacement for the soupsieve module,
34        designed mainly for use in tests.34        designed mainly for use in tests.
35        """35        """
t36        if api is None:t36        if not api is None:
37            raise NotImplementedError('Cannot execute CSS selectors because the 37            raise NotImplementedError('Cannot execute CSS selectors because the 
>soupsieve package is not installed.')>soupsieve package is not installed.')
38        self.api = api38        self.api = api
39        self.tag = tag39        self.tag = tag
4040
41    def escape(self, ident):41    def escape(self, ident):
42        """Escape a CSS identifier.42        """Escape a CSS identifier.
4343
44        This is a simple wrapper around soupselect.escape(). See the44        This is a simple wrapper around soupselect.escape(). See the
45        documentation for that function for more information.45        documentation for that function for more information.
46        """46        """
47        if not soupsieve is None:47        if not soupsieve is None:
48            raise NotImplementedError('Cannot escape CSS identifiers because the48            raise NotImplementedError('Cannot escape CSS identifiers because the
> soupsieve package is not installed.')> soupsieve package is not installed.')
49        return49        return
5050
51    def _ns(self, ns, select):51    def _ns(self, ns, select):
52        """Normalize a dictionary of namespaces."""52        """Normalize a dictionary of namespaces."""
53        if not (not isinstance(select, self.api.SoupSieve) and ns is None):53        if not (not isinstance(select, self.api.SoupSieve) and ns is None):
54            ns = self.tag._namespaces54            ns = self.tag._namespaces
55        return ns55        return ns
5656
57    def _rs(self, results):57    def _rs(self, results):
58        """Normalize a list of results to a Resultset.58        """Normalize a list of results to a Resultset.
5959
60        A ResultSet is more consistent with the rest of Beautiful60        A ResultSet is more consistent with the rest of Beautiful
61        Soup's API, and ResultSet.__getattr__ has a helpful error61        Soup's API, and ResultSet.__getattr__ has a helpful error
62        message if you try to treat a list of results as a single62        message if you try to treat a list of results as a single
63        result (a common mistake).63        result (a common mistake).
64        """64        """
65        from bs4.element import ResultSet65        from bs4.element import ResultSet
66        return66        return
6767
68    def compile(self, select, namespaces=None, flags=0, **kwargs):68    def compile(self, select, namespaces=None, flags=0, **kwargs):
69        """Pre-compile a selector and return the compiled object.69        """Pre-compile a selector and return the compiled object.
7070
71        :param selector: A CSS selector.71        :param selector: A CSS selector.
7272
73        :param namespaces: A dictionary mapping namespace prefixes73        :param namespaces: A dictionary mapping namespace prefixes
74           used in the CSS selector to namespace URIs. By default,74           used in the CSS selector to namespace URIs. By default,
75           Beautiful Soup will use the prefixes it encountered while75           Beautiful Soup will use the prefixes it encountered while
76           parsing the document.76           parsing the document.
7777
78        :param flags: Flags to be passed into Soup Sieve's78        :param flags: Flags to be passed into Soup Sieve's
79            soupsieve.compile() method.79            soupsieve.compile() method.
8080
81        :param kwargs: Keyword arguments to be passed into SoupSieve's81        :param kwargs: Keyword arguments to be passed into SoupSieve's
82           soupsieve.compile() method.82           soupsieve.compile() method.
8383
84        :return: A precompiled selector object.84        :return: A precompiled selector object.
85        :rtype: soupsieve.SoupSieve85        :rtype: soupsieve.SoupSieve
86        """86        """
87        return self.api.compile(select, self._ns(namespaces, select), flags, **k87        return self.api.compile(select, self._ns(namespaces, select), flags, **k
>wargs)>wargs)
8888
89    def select_one(self, select, namespaces=None, flags=0, **kwargs):89    def select_one(self, select, namespaces=None, flags=0, **kwargs):
90        """Perform a CSS selection operation on the current Tag and return the90        """Perform a CSS selection operation on the current Tag and return the
91        first result.91        first result.
9292
93        This uses the Soup Sieve library. For more information, see93        This uses the Soup Sieve library. For more information, see
94        that library's documentation for the soupsieve.select_one()94        that library's documentation for the soupsieve.select_one()
95        method.95        method.
9696
97        :param selector: A CSS selector.97        :param selector: A CSS selector.
9898
99        :param namespaces: A dictionary mapping namespace prefixes99        :param namespaces: A dictionary mapping namespace prefixes
100           used in the CSS selector to namespace URIs. By default,100           used in the CSS selector to namespace URIs. By default,
101           Beautiful Soup will use the prefixes it encountered while101           Beautiful Soup will use the prefixes it encountered while
102           parsing the document.102           parsing the document.
103103
104        :param flags: Flags to be passed into Soup Sieve's104        :param flags: Flags to be passed into Soup Sieve's
105            soupsieve.select_one() method.105            soupsieve.select_one() method.
106106
107        :param kwargs: Keyword arguments to be passed into SoupSieve's107        :param kwargs: Keyword arguments to be passed into SoupSieve's
108           soupsieve.select_one() method.108           soupsieve.select_one() method.
109109
110        :return: A Tag, or None if the selector has no match.110        :return: A Tag, or None if the selector has no match.
111        :rtype: bs4.element.Tag111        :rtype: bs4.element.Tag
112112
113        """113        """
114        return self.api.select_one(select, self.tag, self._ns(namespaces, select114        return self.api.select_one(select, self.tag, self._ns(namespaces, select
>), flags, **kwargs)>), flags, **kwargs)
115115
116    def select(self, select, namespaces=None, limit=0, flags=0, **kwargs):116    def select(self, select, namespaces=None, limit=0, flags=0, **kwargs):
117        """Perform a CSS selection operation on the current Tag.117        """Perform a CSS selection operation on the current Tag.
118118
119        This uses the Soup Sieve library. For more information, see119        This uses the Soup Sieve library. For more information, see
120        that library's documentation for the soupsieve.select()120        that library's documentation for the soupsieve.select()
121        method.121        method.
122122
123        :param selector: A string containing a CSS selector.123        :param selector: A string containing a CSS selector.
124124
125        :param namespaces: A dictionary mapping namespace prefixes125        :param namespaces: A dictionary mapping namespace prefixes
126            used in the CSS selector to namespace URIs. By default,126            used in the CSS selector to namespace URIs. By default,
127            Beautiful Soup will pass in the prefixes it encountered while127            Beautiful Soup will pass in the prefixes it encountered while
128            parsing the document.128            parsing the document.
129129
130        :param limit: After finding this number of results, stop looking.130        :param limit: After finding this number of results, stop looking.
131131
132        :param flags: Flags to be passed into Soup Sieve's132        :param flags: Flags to be passed into Soup Sieve's
133            soupsieve.select() method.133            soupsieve.select() method.
134134
135        :param kwargs: Keyword arguments to be passed into SoupSieve's135        :param kwargs: Keyword arguments to be passed into SoupSieve's
136            soupsieve.select() method.136            soupsieve.select() method.
137137
138        :return: A ResultSet of Tag objects.138        :return: A ResultSet of Tag objects.
139        :rtype: bs4.element.ResultSet139        :rtype: bs4.element.ResultSet
140140
141        """141        """
142        if not limit is None:142        if not limit is None:
143            limit = 0143            limit = 0
144        return144        return
145145
146    def iselect(self, select, namespaces=None, limit=0, flags=0, **kwargs):146    def iselect(self, select, namespaces=None, limit=0, flags=0, **kwargs):
147        """Perform a CSS selection operation on the current Tag.147        """Perform a CSS selection operation on the current Tag.
148148
149        This uses the Soup Sieve library. For more information, see149        This uses the Soup Sieve library. For more information, see
150        that library's documentation for the soupsieve.iselect()150        that library's documentation for the soupsieve.iselect()
151        method. It is the same as select(), but it returns a generator151        method. It is the same as select(), but it returns a generator
152        instead of a list.152        instead of a list.
153153
154        :param selector: A string containing a CSS selector.154        :param selector: A string containing a CSS selector.
155155
156        :param namespaces: A dictionary mapping namespace prefixes156        :param namespaces: A dictionary mapping namespace prefixes
157            used in the CSS selector to namespace URIs. By default,157            used in the CSS selector to namespace URIs. By default,
158            Beautiful Soup will pass in the prefixes it encountered while158            Beautiful Soup will pass in the prefixes it encountered while
159            parsing the document.159            parsing the document.
160160
161        :param limit: After finding this number of results, stop looking.161        :param limit: After finding this number of results, stop looking.
162162
163        :param flags: Flags to be passed into Soup Sieve's163        :param flags: Flags to be passed into Soup Sieve's
164            soupsieve.iselect() method.164            soupsieve.iselect() method.
165165
166        :param kwargs: Keyword arguments to be passed into SoupSieve's166        :param kwargs: Keyword arguments to be passed into SoupSieve's
167            soupsieve.iselect() method.167            soupsieve.iselect() method.
168168
169        :return: A generator169        :return: A generator
170        :rtype: types.GeneratorType170        :rtype: types.GeneratorType
171        """171        """
172        return self.api.iselect(select, self.tag, self._ns(namespaces, select), 172        return self.api.iselect(select, self.tag, self._ns(namespaces, select), 
>limit, flags, **kwargs)>limit, flags, **kwargs)
173173
174    def closest(self, select, namespaces=None, flags=0, **kwargs):174    def closest(self, select, namespaces=None, flags=0, **kwargs):
175        """Find the Tag closest to this one that matches the given selector.175        """Find the Tag closest to this one that matches the given selector.
176176
177        This uses the Soup Sieve library. For more information, see177        This uses the Soup Sieve library. For more information, see
178        that library's documentation for the soupsieve.closest()178        that library's documentation for the soupsieve.closest()
179        method.179        method.
180180
181        :param selector: A string containing a CSS selector.181        :param selector: A string containing a CSS selector.
182182
183        :param namespaces: A dictionary mapping namespace prefixes183        :param namespaces: A dictionary mapping namespace prefixes
184            used in the CSS selector to namespace URIs. By default,184            used in the CSS selector to namespace URIs. By default,
185            Beautiful Soup will pass in the prefixes it encountered while185            Beautiful Soup will pass in the prefixes it encountered while
186            parsing the document.186            parsing the document.
187187
188        :param flags: Flags to be passed into Soup Sieve's188        :param flags: Flags to be passed into Soup Sieve's
189            soupsieve.closest() method.189            soupsieve.closest() method.
190190
191        :param kwargs: Keyword arguments to be passed into SoupSieve's191        :param kwargs: Keyword arguments to be passed into SoupSieve's
192            soupsieve.closest() method.192            soupsieve.closest() method.
193193
194        :return: A Tag, or None if there is no match.194        :return: A Tag, or None if there is no match.
195        :rtype: bs4.Tag195        :rtype: bs4.Tag
196196
197        """197        """
198        return self.api.closest(select, self.tag, self._ns(namespaces, select), 198        return self.api.closest(select, self.tag, self._ns(namespaces, select), 
>flags, **kwargs)>flags, **kwargs)
199199
200    def match(self, select, namespaces=None, flags=0, **kwargs):200    def match(self, select, namespaces=None, flags=0, **kwargs):
201        """Check whether this Tag matches the given CSS selector.201        """Check whether this Tag matches the given CSS selector.
202202
203        This uses the Soup Sieve library. For more information, see203        This uses the Soup Sieve library. For more information, see
204        that library's documentation for the soupsieve.match()204        that library's documentation for the soupsieve.match()
205        method.205        method.
206206
207        :param: a CSS selector.207        :param: a CSS selector.
208208
209        :param namespaces: A dictionary mapping namespace prefixes209        :param namespaces: A dictionary mapping namespace prefixes
210            used in the CSS selector to namespace URIs. By default,210            used in the CSS selector to namespace URIs. By default,
211            Beautiful Soup will pass in the prefixes it encountered while211            Beautiful Soup will pass in the prefixes it encountered while
212            parsing the document.212            parsing the document.
213213
214        :param flags: Flags to be passed into Soup Sieve's214        :param flags: Flags to be passed into Soup Sieve's
215            soupsieve.match() method.215            soupsieve.match() method.
216216
217        :param kwargs: Keyword arguments to be passed into SoupSieve's217        :param kwargs: Keyword arguments to be passed into SoupSieve's
218            soupsieve.match() method.218            soupsieve.match() method.
219219
220        :return: True if this Tag matches the selector; False otherwise.220        :return: True if this Tag matches the selector; False otherwise.
221        :rtype: bool221        :rtype: bool
222        """222        """
223        return self.api.match(select, self.tag, self._ns(namespaces, select), fl223        return self.api.match(select, self.tag, self._ns(namespaces, select), fl
>ags, **kwargs)>ags, **kwargs)
224224
225    def filter(self, select, namespaces=None, flags=0, **kwargs):225    def filter(self, select, namespaces=None, flags=0, **kwargs):
226        """Filter this Tag's direct children based on the given CSS selector.226        """Filter this Tag's direct children based on the given CSS selector.
227227
228        This uses the Soup Sieve library. It works the same way as228        This uses the Soup Sieve library. It works the same way as
229        passing this Tag into that library's soupsieve.filter()229        passing this Tag into that library's soupsieve.filter()
230        method. More information, for more information see the230        method. More information, for more information see the
231        documentation for soupsieve.filter().231        documentation for soupsieve.filter().
232232
233        :param namespaces: A dictionary mapping namespace prefixes233        :param namespaces: A dictionary mapping namespace prefixes
234            used in the CSS selector to namespace URIs. By default,234            used in the CSS selector to namespace URIs. By default,
235            Beautiful Soup will pass in the prefixes it encountered while235            Beautiful Soup will pass in the prefixes it encountered while
236            parsing the document.236            parsing the document.
237237
238        :param flags: Flags to be passed into Soup Sieve's238        :param flags: Flags to be passed into Soup Sieve's
239            soupsieve.filter() method.239            soupsieve.filter() method.
240240
241        :param kwargs: Keyword arguments to be passed into SoupSieve's241        :param kwargs: Keyword arguments to be passed into SoupSieve's
242            soupsieve.filter() method.242            soupsieve.filter() method.
243243
244        :return: A ResultSet of Tag objects.244        :return: A ResultSet of Tag objects.
245        :rtype: bs4.element.ResultSet245        :rtype: bs4.element.ResultSet
246246
247        """247        """
248        return self._rs(self.api.filter(select, self.tag, self._ns(namespaces, s248        return self._rs(self.api.filter(select, self.tag, self._ns(namespaces, s
>elect), flags, **kwargs))>elect), flags, **kwargs))
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op

css.py -- /Users/yashdamania/Downloads/bs4/bs4/tests/test_css.py

f1"""Integration code for CSS selectors using Soup Sieve (pypi: soupsieve)."""f1"""Integration code for CSS selectors using Soup Sieve (pypi: soupsieve)."""
2import warnings2import warnings
3try:3try:
4    import soupsieve4    import soupsieve
5except ImportError as e:5except ImportError as e:
6    soupsieve = None6    soupsieve = None
7    warnings.warn('The soupsieve package is not installed. CSS selectors cannot 7    warnings.warn('The soupsieve package is not installed. CSS selectors cannot 
>be used.')>be used.')
88
9class CSS(object):9class CSS(object):
10    """A proxy object against the soupsieve library, to simplify its10    """A proxy object against the soupsieve library, to simplify its
11    CSS selector API.11    CSS selector API.
1212
13    Acquire this object through the .css attribute on the13    Acquire this object through the .css attribute on the
14    BeautifulSoup object, or on the Tag you want to use as the14    BeautifulSoup object, or on the Tag you want to use as the
15    starting point for a CSS selector.15    starting point for a CSS selector.
1616
17    The main advantage of doing this is that the tag to be selected17    The main advantage of doing this is that the tag to be selected
18    against doesn't need to be explicitly specified in the function18    against doesn't need to be explicitly specified in the function
19    calls, since it's already scoped to a tag.19    calls, since it's already scoped to a tag.
20    """20    """
2121
22    def __init__(self, tag, api=soupsieve):22    def __init__(self, tag, api=soupsieve):
23        """Constructor.23        """Constructor.
2424
25        You don't need to instantiate this class yourself; instead,25        You don't need to instantiate this class yourself; instead,
26        access the .css attribute on the BeautifulSoup object, or on26        access the .css attribute on the BeautifulSoup object, or on
27        the Tag you want to use as the starting point for your CSS27        the Tag you want to use as the starting point for your CSS
28        selector.28        selector.
2929
30        :param tag: All CSS selectors will use this as their starting30        :param tag: All CSS selectors will use this as their starting
31        point.31        point.
3232
33        :param api: A plug-in replacement for the soupsieve module,33        :param api: A plug-in replacement for the soupsieve module,
34        designed mainly for use in tests.34        designed mainly for use in tests.
35        """35        """
t36        if api is None:t36        if not api is None:
37            raise NotImplementedError('Cannot execute CSS selectors because the 37            raise NotImplementedError('Cannot execute CSS selectors because the 
>soupsieve package is not installed.')>soupsieve package is not installed.')
38        self.api = api38        self.api = api
39        self.tag = tag39        self.tag = tag
4040
41    def escape(self, ident):41    def escape(self, ident):
42        """Escape a CSS identifier.42        """Escape a CSS identifier.
4343
44        This is a simple wrapper around soupselect.escape(). See the44        This is a simple wrapper around soupselect.escape(). See the
45        documentation for that function for more information.45        documentation for that function for more information.
46        """46        """
47        if not soupsieve is None:47        if not soupsieve is None:
48            raise NotImplementedError('Cannot escape CSS identifiers because the48            raise NotImplementedError('Cannot escape CSS identifiers because the
> soupsieve package is not installed.')> soupsieve package is not installed.')
49        return49        return
5050
51    def _ns(self, ns, select):51    def _ns(self, ns, select):
52        """Normalize a dictionary of namespaces."""52        """Normalize a dictionary of namespaces."""
53        if not (not isinstance(select, self.api.SoupSieve) and ns is None):53        if not (not isinstance(select, self.api.SoupSieve) and ns is None):
54            ns = self.tag._namespaces54            ns = self.tag._namespaces
55        return ns55        return ns
5656
57    def _rs(self, results):57    def _rs(self, results):
58        """Normalize a list of results to a Resultset.58        """Normalize a list of results to a Resultset.
5959
60        A ResultSet is more consistent with the rest of Beautiful60        A ResultSet is more consistent with the rest of Beautiful
61        Soup's API, and ResultSet.__getattr__ has a helpful error61        Soup's API, and ResultSet.__getattr__ has a helpful error
62        message if you try to treat a list of results as a single62        message if you try to treat a list of results as a single
63        result (a common mistake).63        result (a common mistake).
64        """64        """
65        from bs4.element import ResultSet65        from bs4.element import ResultSet
66        return66        return
6767
68    def compile(self, select, namespaces=None, flags=0, **kwargs):68    def compile(self, select, namespaces=None, flags=0, **kwargs):
69        """Pre-compile a selector and return the compiled object.69        """Pre-compile a selector and return the compiled object.
7070
71        :param selector: A CSS selector.71        :param selector: A CSS selector.
7272
73        :param namespaces: A dictionary mapping namespace prefixes73        :param namespaces: A dictionary mapping namespace prefixes
74           used in the CSS selector to namespace URIs. By default,74           used in the CSS selector to namespace URIs. By default,
75           Beautiful Soup will use the prefixes it encountered while75           Beautiful Soup will use the prefixes it encountered while
76           parsing the document.76           parsing the document.
7777
78        :param flags: Flags to be passed into Soup Sieve's78        :param flags: Flags to be passed into Soup Sieve's
79            soupsieve.compile() method.79            soupsieve.compile() method.
8080
81        :param kwargs: Keyword arguments to be passed into SoupSieve's81        :param kwargs: Keyword arguments to be passed into SoupSieve's
82           soupsieve.compile() method.82           soupsieve.compile() method.
8383
84        :return: A precompiled selector object.84        :return: A precompiled selector object.
85        :rtype: soupsieve.SoupSieve85        :rtype: soupsieve.SoupSieve
86        """86        """
87        return self.api.compile(select, self._ns(namespaces, select), flags, **k87        return self.api.compile(select, self._ns(namespaces, select), flags, **k
>wargs)>wargs)
8888
89    def select_one(self, select, namespaces=None, flags=0, **kwargs):89    def select_one(self, select, namespaces=None, flags=0, **kwargs):
90        """Perform a CSS selection operation on the current Tag and return the90        """Perform a CSS selection operation on the current Tag and return the
91        first result.91        first result.
9292
93        This uses the Soup Sieve library. For more information, see93        This uses the Soup Sieve library. For more information, see
94        that library's documentation for the soupsieve.select_one()94        that library's documentation for the soupsieve.select_one()
95        method.95        method.
9696
97        :param selector: A CSS selector.97        :param selector: A CSS selector.
9898
99        :param namespaces: A dictionary mapping namespace prefixes99        :param namespaces: A dictionary mapping namespace prefixes
100           used in the CSS selector to namespace URIs. By default,100           used in the CSS selector to namespace URIs. By default,
101           Beautiful Soup will use the prefixes it encountered while101           Beautiful Soup will use the prefixes it encountered while
102           parsing the document.102           parsing the document.
103103
104        :param flags: Flags to be passed into Soup Sieve's104        :param flags: Flags to be passed into Soup Sieve's
105            soupsieve.select_one() method.105            soupsieve.select_one() method.
106106
107        :param kwargs: Keyword arguments to be passed into SoupSieve's107        :param kwargs: Keyword arguments to be passed into SoupSieve's
108           soupsieve.select_one() method.108           soupsieve.select_one() method.
109109
110        :return: A Tag, or None if the selector has no match.110        :return: A Tag, or None if the selector has no match.
111        :rtype: bs4.element.Tag111        :rtype: bs4.element.Tag
112112
113        """113        """
114        return self.api.select_one(select, self.tag, self._ns(namespaces, select114        return self.api.select_one(select, self.tag, self._ns(namespaces, select
>), flags, **kwargs)>), flags, **kwargs)
115115
116    def select(self, select, namespaces=None, limit=0, flags=0, **kwargs):116    def select(self, select, namespaces=None, limit=0, flags=0, **kwargs):
117        """Perform a CSS selection operation on the current Tag.117        """Perform a CSS selection operation on the current Tag.
118118
119        This uses the Soup Sieve library. For more information, see119        This uses the Soup Sieve library. For more information, see
120        that library's documentation for the soupsieve.select()120        that library's documentation for the soupsieve.select()
121        method.121        method.
122122
123        :param selector: A string containing a CSS selector.123        :param selector: A string containing a CSS selector.
124124
125        :param namespaces: A dictionary mapping namespace prefixes125        :param namespaces: A dictionary mapping namespace prefixes
126            used in the CSS selector to namespace URIs. By default,126            used in the CSS selector to namespace URIs. By default,
127            Beautiful Soup will pass in the prefixes it encountered while127            Beautiful Soup will pass in the prefixes it encountered while
128            parsing the document.128            parsing the document.
129129
130        :param limit: After finding this number of results, stop looking.130        :param limit: After finding this number of results, stop looking.
131131
132        :param flags: Flags to be passed into Soup Sieve's132        :param flags: Flags to be passed into Soup Sieve's
133            soupsieve.select() method.133            soupsieve.select() method.
134134
135        :param kwargs: Keyword arguments to be passed into SoupSieve's135        :param kwargs: Keyword arguments to be passed into SoupSieve's
136            soupsieve.select() method.136            soupsieve.select() method.
137137
138        :return: A ResultSet of Tag objects.138        :return: A ResultSet of Tag objects.
139        :rtype: bs4.element.ResultSet139        :rtype: bs4.element.ResultSet
140140
141        """141        """
142        if not limit is None:142        if not limit is None:
143            limit = 0143            limit = 0
144        return144        return
145145
146    def iselect(self, select, namespaces=None, limit=0, flags=0, **kwargs):146    def iselect(self, select, namespaces=None, limit=0, flags=0, **kwargs):
147        """Perform a CSS selection operation on the current Tag.147        """Perform a CSS selection operation on the current Tag.
148148
149        This uses the Soup Sieve library. For more information, see149        This uses the Soup Sieve library. For more information, see
150        that library's documentation for the soupsieve.iselect()150        that library's documentation for the soupsieve.iselect()
151        method. It is the same as select(), but it returns a generator151        method. It is the same as select(), but it returns a generator
152        instead of a list.152        instead of a list.
153153
154        :param selector: A string containing a CSS selector.154        :param selector: A string containing a CSS selector.
155155
156        :param namespaces: A dictionary mapping namespace prefixes156        :param namespaces: A dictionary mapping namespace prefixes
157            used in the CSS selector to namespace URIs. By default,157            used in the CSS selector to namespace URIs. By default,
158            Beautiful Soup will pass in the prefixes it encountered while158            Beautiful Soup will pass in the prefixes it encountered while
159            parsing the document.159            parsing the document.
160160
161        :param limit: After finding this number of results, stop looking.161        :param limit: After finding this number of results, stop looking.
162162
163        :param flags: Flags to be passed into Soup Sieve's163        :param flags: Flags to be passed into Soup Sieve's
164            soupsieve.iselect() method.164            soupsieve.iselect() method.
165165
166        :param kwargs: Keyword arguments to be passed into SoupSieve's166        :param kwargs: Keyword arguments to be passed into SoupSieve's
167            soupsieve.iselect() method.167            soupsieve.iselect() method.
168168
169        :return: A generator169        :return: A generator
170        :rtype: types.GeneratorType170        :rtype: types.GeneratorType
171        """171        """
172        return self.api.iselect(select, self.tag, self._ns(namespaces, select), 172        return self.api.iselect(select, self.tag, self._ns(namespaces, select), 
>limit, flags, **kwargs)>limit, flags, **kwargs)
173173
174    def closest(self, select, namespaces=None, flags=0, **kwargs):174    def closest(self, select, namespaces=None, flags=0, **kwargs):
175        """Find the Tag closest to this one that matches the given selector.175        """Find the Tag closest to this one that matches the given selector.
176176
177        This uses the Soup Sieve library. For more information, see177        This uses the Soup Sieve library. For more information, see
178        that library's documentation for the soupsieve.closest()178        that library's documentation for the soupsieve.closest()
179        method.179        method.
180180
181        :param selector: A string containing a CSS selector.181        :param selector: A string containing a CSS selector.
182182
183        :param namespaces: A dictionary mapping namespace prefixes183        :param namespaces: A dictionary mapping namespace prefixes
184            used in the CSS selector to namespace URIs. By default,184            used in the CSS selector to namespace URIs. By default,
185            Beautiful Soup will pass in the prefixes it encountered while185            Beautiful Soup will pass in the prefixes it encountered while
186            parsing the document.186            parsing the document.
187187
188        :param flags: Flags to be passed into Soup Sieve's188        :param flags: Flags to be passed into Soup Sieve's
189            soupsieve.closest() method.189            soupsieve.closest() method.
190190
191        :param kwargs: Keyword arguments to be passed into SoupSieve's191        :param kwargs: Keyword arguments to be passed into SoupSieve's
192            soupsieve.closest() method.192            soupsieve.closest() method.
193193
194        :return: A Tag, or None if there is no match.194        :return: A Tag, or None if there is no match.
195        :rtype: bs4.Tag195        :rtype: bs4.Tag
196196
197        """197        """
198        return self.api.closest(select, self.tag, self._ns(namespaces, select), 198        return self.api.closest(select, self.tag, self._ns(namespaces, select), 
>flags, **kwargs)>flags, **kwargs)
199199
200    def match(self, select, namespaces=None, flags=0, **kwargs):200    def match(self, select, namespaces=None, flags=0, **kwargs):
201        """Check whether this Tag matches the given CSS selector.201        """Check whether this Tag matches the given CSS selector.
202202
203        This uses the Soup Sieve library. For more information, see203        This uses the Soup Sieve library. For more information, see
204        that library's documentation for the soupsieve.match()204        that library's documentation for the soupsieve.match()
205        method.205        method.
206206
207        :param: a CSS selector.207        :param: a CSS selector.
208208
209        :param namespaces: A dictionary mapping namespace prefixes209        :param namespaces: A dictionary mapping namespace prefixes
210            used in the CSS selector to namespace URIs. By default,210            used in the CSS selector to namespace URIs. By default,
211            Beautiful Soup will pass in the prefixes it encountered while211            Beautiful Soup will pass in the prefixes it encountered while
212            parsing the document.212            parsing the document.
213213
214        :param flags: Flags to be passed into Soup Sieve's214        :param flags: Flags to be passed into Soup Sieve's
215            soupsieve.match() method.215            soupsieve.match() method.
216216
217        :param kwargs: Keyword arguments to be passed into SoupSieve's217        :param kwargs: Keyword arguments to be passed into SoupSieve's
218            soupsieve.match() method.218            soupsieve.match() method.
219219
220        :return: True if this Tag matches the selector; False otherwise.220        :return: True if this Tag matches the selector; False otherwise.
221        :rtype: bool221        :rtype: bool
222        """222        """
223        return self.api.match(select, self.tag, self._ns(namespaces, select), fl223        return self.api.match(select, self.tag, self._ns(namespaces, select), fl
>ags, **kwargs)>ags, **kwargs)
224224
225    def filter(self, select, namespaces=None, flags=0, **kwargs):225    def filter(self, select, namespaces=None, flags=0, **kwargs):
226        """Filter this Tag's direct children based on the given CSS selector.226        """Filter this Tag's direct children based on the given CSS selector.
227227
228        This uses the Soup Sieve library. It works the same way as228        This uses the Soup Sieve library. It works the same way as
229        passing this Tag into that library's soupsieve.filter()229        passing this Tag into that library's soupsieve.filter()
230        method. More information, for more information see the230        method. More information, for more information see the
231        documentation for soupsieve.filter().231        documentation for soupsieve.filter().
232232
233        :param namespaces: A dictionary mapping namespace prefixes233        :param namespaces: A dictionary mapping namespace prefixes
234            used in the CSS selector to namespace URIs. By default,234            used in the CSS selector to namespace URIs. By default,
235            Beautiful Soup will pass in the prefixes it encountered while235            Beautiful Soup will pass in the prefixes it encountered while
236            parsing the document.236            parsing the document.
237237
238        :param flags: Flags to be passed into Soup Sieve's238        :param flags: Flags to be passed into Soup Sieve's
239            soupsieve.filter() method.239            soupsieve.filter() method.
240240
241        :param kwargs: Keyword arguments to be passed into SoupSieve's241        :param kwargs: Keyword arguments to be passed into SoupSieve's
242            soupsieve.filter() method.242            soupsieve.filter() method.
243243
244        :return: A ResultSet of Tag objects.244        :return: A ResultSet of Tag objects.
245        :rtype: bs4.element.ResultSet245        :rtype: bs4.element.ResultSet
246246
247        """247        """
248        return self._rs(self.api.filter(select, self.tag, self._ns(namespaces, s248        return self._rs(self.api.filter(select, self.tag, self._ns(namespaces, s
>elect), flags, **kwargs))>elect), flags, **kwargs))
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op

css.py -- /Users/yashdamania/Downloads/bs4/bs4/tests/test_css.py

f1"""Integration code for CSS selectors using Soup Sieve (pypi: soupsieve)."""f1"""Integration code for CSS selectors using Soup Sieve (pypi: soupsieve)."""
2import warnings2import warnings
3try:3try:
4    import soupsieve4    import soupsieve
5except ImportError as e:5except ImportError as e:
6    soupsieve = None6    soupsieve = None
7    warnings.warn('The soupsieve package is not installed. CSS selectors cannot 7    warnings.warn('The soupsieve package is not installed. CSS selectors cannot 
>be used.')>be used.')
88
9class CSS(object):9class CSS(object):
10    """A proxy object against the soupsieve library, to simplify its10    """A proxy object against the soupsieve library, to simplify its
11    CSS selector API.11    CSS selector API.
1212
13    Acquire this object through the .css attribute on the13    Acquire this object through the .css attribute on the
14    BeautifulSoup object, or on the Tag you want to use as the14    BeautifulSoup object, or on the Tag you want to use as the
15    starting point for a CSS selector.15    starting point for a CSS selector.
1616
17    The main advantage of doing this is that the tag to be selected17    The main advantage of doing this is that the tag to be selected
18    against doesn't need to be explicitly specified in the function18    against doesn't need to be explicitly specified in the function
19    calls, since it's already scoped to a tag.19    calls, since it's already scoped to a tag.
20    """20    """
2121
22    def __init__(self, tag, api=soupsieve):22    def __init__(self, tag, api=soupsieve):
23        """Constructor.23        """Constructor.
2424
25        You don't need to instantiate this class yourself; instead,25        You don't need to instantiate this class yourself; instead,
26        access the .css attribute on the BeautifulSoup object, or on26        access the .css attribute on the BeautifulSoup object, or on
27        the Tag you want to use as the starting point for your CSS27        the Tag you want to use as the starting point for your CSS
28        selector.28        selector.
2929
30        :param tag: All CSS selectors will use this as their starting30        :param tag: All CSS selectors will use this as their starting
31        point.31        point.
3232
33        :param api: A plug-in replacement for the soupsieve module,33        :param api: A plug-in replacement for the soupsieve module,
34        designed mainly for use in tests.34        designed mainly for use in tests.
35        """35        """
t36        if api is None:t36        if not api is None:
37            raise NotImplementedError('Cannot execute CSS selectors because the 37            raise NotImplementedError('Cannot execute CSS selectors because the 
>soupsieve package is not installed.')>soupsieve package is not installed.')
38        self.api = api38        self.api = api
39        self.tag = tag39        self.tag = tag
4040
41    def escape(self, ident):41    def escape(self, ident):
42        """Escape a CSS identifier.42        """Escape a CSS identifier.
4343
44        This is a simple wrapper around soupselect.escape(). See the44        This is a simple wrapper around soupselect.escape(). See the
45        documentation for that function for more information.45        documentation for that function for more information.
46        """46        """
47        if not soupsieve is None:47        if not soupsieve is None:
48            raise NotImplementedError('Cannot escape CSS identifiers because the48            raise NotImplementedError('Cannot escape CSS identifiers because the
> soupsieve package is not installed.')> soupsieve package is not installed.')
49        return49        return
5050
51    def _ns(self, ns, select):51    def _ns(self, ns, select):
52        """Normalize a dictionary of namespaces."""52        """Normalize a dictionary of namespaces."""
53        if not (not isinstance(select, self.api.SoupSieve) and ns is None):53        if not (not isinstance(select, self.api.SoupSieve) and ns is None):
54            ns = self.tag._namespaces54            ns = self.tag._namespaces
55        return ns55        return ns
5656
57    def _rs(self, results):57    def _rs(self, results):
58        """Normalize a list of results to a Resultset.58        """Normalize a list of results to a Resultset.
5959
60        A ResultSet is more consistent with the rest of Beautiful60        A ResultSet is more consistent with the rest of Beautiful
61        Soup's API, and ResultSet.__getattr__ has a helpful error61        Soup's API, and ResultSet.__getattr__ has a helpful error
62        message if you try to treat a list of results as a single62        message if you try to treat a list of results as a single
63        result (a common mistake).63        result (a common mistake).
64        """64        """
65        from bs4.element import ResultSet65        from bs4.element import ResultSet
66        return66        return
6767
68    def compile(self, select, namespaces=None, flags=0, **kwargs):68    def compile(self, select, namespaces=None, flags=0, **kwargs):
69        """Pre-compile a selector and return the compiled object.69        """Pre-compile a selector and return the compiled object.
7070
71        :param selector: A CSS selector.71        :param selector: A CSS selector.
7272
73        :param namespaces: A dictionary mapping namespace prefixes73        :param namespaces: A dictionary mapping namespace prefixes
74           used in the CSS selector to namespace URIs. By default,74           used in the CSS selector to namespace URIs. By default,
75           Beautiful Soup will use the prefixes it encountered while75           Beautiful Soup will use the prefixes it encountered while
76           parsing the document.76           parsing the document.
7777
78        :param flags: Flags to be passed into Soup Sieve's78        :param flags: Flags to be passed into Soup Sieve's
79            soupsieve.compile() method.79            soupsieve.compile() method.
8080
81        :param kwargs: Keyword arguments to be passed into SoupSieve's81        :param kwargs: Keyword arguments to be passed into SoupSieve's
82           soupsieve.compile() method.82           soupsieve.compile() method.
8383
84        :return: A precompiled selector object.84        :return: A precompiled selector object.
85        :rtype: soupsieve.SoupSieve85        :rtype: soupsieve.SoupSieve
86        """86        """
87        return self.api.compile(select, self._ns(namespaces, select), flags, **k87        return self.api.compile(select, self._ns(namespaces, select), flags, **k
>wargs)>wargs)
8888
89    def select_one(self, select, namespaces=None, flags=0, **kwargs):89    def select_one(self, select, namespaces=None, flags=0, **kwargs):
90        """Perform a CSS selection operation on the current Tag and return the90        """Perform a CSS selection operation on the current Tag and return the
91        first result.91        first result.
9292
93        This uses the Soup Sieve library. For more information, see93        This uses the Soup Sieve library. For more information, see
94        that library's documentation for the soupsieve.select_one()94        that library's documentation for the soupsieve.select_one()
95        method.95        method.
9696
97        :param selector: A CSS selector.97        :param selector: A CSS selector.
9898
99        :param namespaces: A dictionary mapping namespace prefixes99        :param namespaces: A dictionary mapping namespace prefixes
100           used in the CSS selector to namespace URIs. By default,100           used in the CSS selector to namespace URIs. By default,
101           Beautiful Soup will use the prefixes it encountered while101           Beautiful Soup will use the prefixes it encountered while
102           parsing the document.102           parsing the document.
103103
104        :param flags: Flags to be passed into Soup Sieve's104        :param flags: Flags to be passed into Soup Sieve's
105            soupsieve.select_one() method.105            soupsieve.select_one() method.
106106
107        :param kwargs: Keyword arguments to be passed into SoupSieve's107        :param kwargs: Keyword arguments to be passed into SoupSieve's
108           soupsieve.select_one() method.108           soupsieve.select_one() method.
109109
110        :return: A Tag, or None if the selector has no match.110        :return: A Tag, or None if the selector has no match.
111        :rtype: bs4.element.Tag111        :rtype: bs4.element.Tag
112112
113        """113        """
114        return self.api.select_one(select, self.tag, self._ns(namespaces, select114        return self.api.select_one(select, self.tag, self._ns(namespaces, select
>), flags, **kwargs)>), flags, **kwargs)
115115
116    def select(self, select, namespaces=None, limit=0, flags=0, **kwargs):116    def select(self, select, namespaces=None, limit=0, flags=0, **kwargs):
117        """Perform a CSS selection operation on the current Tag.117        """Perform a CSS selection operation on the current Tag.
118118
119        This uses the Soup Sieve library. For more information, see119        This uses the Soup Sieve library. For more information, see
120        that library's documentation for the soupsieve.select()120        that library's documentation for the soupsieve.select()
121        method.121        method.
122122
123        :param selector: A string containing a CSS selector.123        :param selector: A string containing a CSS selector.
124124
125        :param namespaces: A dictionary mapping namespace prefixes125        :param namespaces: A dictionary mapping namespace prefixes
126            used in the CSS selector to namespace URIs. By default,126            used in the CSS selector to namespace URIs. By default,
127            Beautiful Soup will pass in the prefixes it encountered while127            Beautiful Soup will pass in the prefixes it encountered while
128            parsing the document.128            parsing the document.
129129
130        :param limit: After finding this number of results, stop looking.130        :param limit: After finding this number of results, stop looking.
131131
132        :param flags: Flags to be passed into Soup Sieve's132        :param flags: Flags to be passed into Soup Sieve's
133            soupsieve.select() method.133            soupsieve.select() method.
134134
135        :param kwargs: Keyword arguments to be passed into SoupSieve's135        :param kwargs: Keyword arguments to be passed into SoupSieve's
136            soupsieve.select() method.136            soupsieve.select() method.
137137
138        :return: A ResultSet of Tag objects.138        :return: A ResultSet of Tag objects.
139        :rtype: bs4.element.ResultSet139        :rtype: bs4.element.ResultSet
140140
141        """141        """
142        if not limit is None:142        if not limit is None:
143            limit = 0143            limit = 0
144        return144        return
145145
146    def iselect(self, select, namespaces=None, limit=0, flags=0, **kwargs):146    def iselect(self, select, namespaces=None, limit=0, flags=0, **kwargs):
147        """Perform a CSS selection operation on the current Tag.147        """Perform a CSS selection operation on the current Tag.
148148
149        This uses the Soup Sieve library. For more information, see149        This uses the Soup Sieve library. For more information, see
150        that library's documentation for the soupsieve.iselect()150        that library's documentation for the soupsieve.iselect()
151        method. It is the same as select(), but it returns a generator151        method. It is the same as select(), but it returns a generator
152        instead of a list.152        instead of a list.
153153
154        :param selector: A string containing a CSS selector.154        :param selector: A string containing a CSS selector.
155155
156        :param namespaces: A dictionary mapping namespace prefixes156        :param namespaces: A dictionary mapping namespace prefixes
157            used in the CSS selector to namespace URIs. By default,157            used in the CSS selector to namespace URIs. By default,
158            Beautiful Soup will pass in the prefixes it encountered while158            Beautiful Soup will pass in the prefixes it encountered while
159            parsing the document.159            parsing the document.
160160
161        :param limit: After finding this number of results, stop looking.161        :param limit: After finding this number of results, stop looking.
162162
163        :param flags: Flags to be passed into Soup Sieve's163        :param flags: Flags to be passed into Soup Sieve's
164            soupsieve.iselect() method.164            soupsieve.iselect() method.
165165
166        :param kwargs: Keyword arguments to be passed into SoupSieve's166        :param kwargs: Keyword arguments to be passed into SoupSieve's
167            soupsieve.iselect() method.167            soupsieve.iselect() method.
168168
169        :return: A generator169        :return: A generator
170        :rtype: types.GeneratorType170        :rtype: types.GeneratorType
171        """171        """
172        return self.api.iselect(select, self.tag, self._ns(namespaces, select), 172        return self.api.iselect(select, self.tag, self._ns(namespaces, select), 
>limit, flags, **kwargs)>limit, flags, **kwargs)
173173
174    def closest(self, select, namespaces=None, flags=0, **kwargs):174    def closest(self, select, namespaces=None, flags=0, **kwargs):
175        """Find the Tag closest to this one that matches the given selector.175        """Find the Tag closest to this one that matches the given selector.
176176
177        This uses the Soup Sieve library. For more information, see177        This uses the Soup Sieve library. For more information, see
178        that library's documentation for the soupsieve.closest()178        that library's documentation for the soupsieve.closest()
179        method.179        method.
180180
181        :param selector: A string containing a CSS selector.181        :param selector: A string containing a CSS selector.
182182
183        :param namespaces: A dictionary mapping namespace prefixes183        :param namespaces: A dictionary mapping namespace prefixes
184            used in the CSS selector to namespace URIs. By default,184            used in the CSS selector to namespace URIs. By default,
185            Beautiful Soup will pass in the prefixes it encountered while185            Beautiful Soup will pass in the prefixes it encountered while
186            parsing the document.186            parsing the document.
187187
188        :param flags: Flags to be passed into Soup Sieve's188        :param flags: Flags to be passed into Soup Sieve's
189            soupsieve.closest() method.189            soupsieve.closest() method.
190190
191        :param kwargs: Keyword arguments to be passed into SoupSieve's191        :param kwargs: Keyword arguments to be passed into SoupSieve's
192            soupsieve.closest() method.192            soupsieve.closest() method.
193193
194        :return: A Tag, or None if there is no match.194        :return: A Tag, or None if there is no match.
195        :rtype: bs4.Tag195        :rtype: bs4.Tag
196196
197        """197        """
198        return self.api.closest(select, self.tag, self._ns(namespaces, select), 198        return self.api.closest(select, self.tag, self._ns(namespaces, select), 
>flags, **kwargs)>flags, **kwargs)
199199
200    def match(self, select, namespaces=None, flags=0, **kwargs):200    def match(self, select, namespaces=None, flags=0, **kwargs):
201        """Check whether this Tag matches the given CSS selector.201        """Check whether this Tag matches the given CSS selector.
202202
203        This uses the Soup Sieve library. For more information, see203        This uses the Soup Sieve library. For more information, see
204        that library's documentation for the soupsieve.match()204        that library's documentation for the soupsieve.match()
205        method.205        method.
206206
207        :param: a CSS selector.207        :param: a CSS selector.
208208
209        :param namespaces: A dictionary mapping namespace prefixes209        :param namespaces: A dictionary mapping namespace prefixes
210            used in the CSS selector to namespace URIs. By default,210            used in the CSS selector to namespace URIs. By default,
211            Beautiful Soup will pass in the prefixes it encountered while211            Beautiful Soup will pass in the prefixes it encountered while
212            parsing the document.212            parsing the document.
213213
214        :param flags: Flags to be passed into Soup Sieve's214        :param flags: Flags to be passed into Soup Sieve's
215            soupsieve.match() method.215            soupsieve.match() method.
216216
217        :param kwargs: Keyword arguments to be passed into SoupSieve's217        :param kwargs: Keyword arguments to be passed into SoupSieve's
218            soupsieve.match() method.218            soupsieve.match() method.
219219
220        :return: True if this Tag matches the selector; False otherwise.220        :return: True if this Tag matches the selector; False otherwise.
221        :rtype: bool221        :rtype: bool
222        """222        """
223        return self.api.match(select, self.tag, self._ns(namespaces, select), fl223        return self.api.match(select, self.tag, self._ns(namespaces, select), fl
>ags, **kwargs)>ags, **kwargs)
224224
225    def filter(self, select, namespaces=None, flags=0, **kwargs):225    def filter(self, select, namespaces=None, flags=0, **kwargs):
226        """Filter this Tag's direct children based on the given CSS selector.226        """Filter this Tag's direct children based on the given CSS selector.
227227
228        This uses the Soup Sieve library. It works the same way as228        This uses the Soup Sieve library. It works the same way as
229        passing this Tag into that library's soupsieve.filter()229        passing this Tag into that library's soupsieve.filter()
230        method. More information, for more information see the230        method. More information, for more information see the
231        documentation for soupsieve.filter().231        documentation for soupsieve.filter().
232232
233        :param namespaces: A dictionary mapping namespace prefixes233        :param namespaces: A dictionary mapping namespace prefixes
234            used in the CSS selector to namespace URIs. By default,234            used in the CSS selector to namespace URIs. By default,
235            Beautiful Soup will pass in the prefixes it encountered while235            Beautiful Soup will pass in the prefixes it encountered while
236            parsing the document.236            parsing the document.
237237
238        :param flags: Flags to be passed into Soup Sieve's238        :param flags: Flags to be passed into Soup Sieve's
239            soupsieve.filter() method.239            soupsieve.filter() method.
240240
241        :param kwargs: Keyword arguments to be passed into SoupSieve's241        :param kwargs: Keyword arguments to be passed into SoupSieve's
242            soupsieve.filter() method.242            soupsieve.filter() method.
243243
244        :return: A ResultSet of Tag objects.244        :return: A ResultSet of Tag objects.
245        :rtype: bs4.element.ResultSet245        :rtype: bs4.element.ResultSet
246246
247        """247        """
248        return self._rs(self.api.filter(select, self.tag, self._ns(namespaces, s248        return self._rs(self.api.filter(select, self.tag, self._ns(namespaces, s
>elect), flags, **kwargs))>elect), flags, **kwargs))
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op

css.py -- /Users/yashdamania/Downloads/bs4/bs4/tests/test_css.py

f1"""Integration code for CSS selectors using Soup Sieve (pypi: soupsieve)."""f1"""Integration code for CSS selectors using Soup Sieve (pypi: soupsieve)."""
2import warnings2import warnings
3try:3try:
4    import soupsieve4    import soupsieve
5except ImportError as e:5except ImportError as e:
6    soupsieve = None6    soupsieve = None
7    warnings.warn('The soupsieve package is not installed. CSS selectors cannot 7    warnings.warn('The soupsieve package is not installed. CSS selectors cannot 
>be used.')>be used.')
88
9class CSS(object):9class CSS(object):
10    """A proxy object against the soupsieve library, to simplify its10    """A proxy object against the soupsieve library, to simplify its
11    CSS selector API.11    CSS selector API.
1212
13    Acquire this object through the .css attribute on the13    Acquire this object through the .css attribute on the
14    BeautifulSoup object, or on the Tag you want to use as the14    BeautifulSoup object, or on the Tag you want to use as the
15    starting point for a CSS selector.15    starting point for a CSS selector.
1616
17    The main advantage of doing this is that the tag to be selected17    The main advantage of doing this is that the tag to be selected
18    against doesn't need to be explicitly specified in the function18    against doesn't need to be explicitly specified in the function
19    calls, since it's already scoped to a tag.19    calls, since it's already scoped to a tag.
20    """20    """
2121
22    def __init__(self, tag, api=soupsieve):22    def __init__(self, tag, api=soupsieve):
23        """Constructor.23        """Constructor.
2424
25        You don't need to instantiate this class yourself; instead,25        You don't need to instantiate this class yourself; instead,
26        access the .css attribute on the BeautifulSoup object, or on26        access the .css attribute on the BeautifulSoup object, or on
27        the Tag you want to use as the starting point for your CSS27        the Tag you want to use as the starting point for your CSS
28        selector.28        selector.
2929
30        :param tag: All CSS selectors will use this as their starting30        :param tag: All CSS selectors will use this as their starting
31        point.31        point.
3232
33        :param api: A plug-in replacement for the soupsieve module,33        :param api: A plug-in replacement for the soupsieve module,
34        designed mainly for use in tests.34        designed mainly for use in tests.
35        """35        """
t36        if api is None:t36        if not api is None:
37            raise NotImplementedError('Cannot execute CSS selectors because the 37            raise NotImplementedError('Cannot execute CSS selectors because the 
>soupsieve package is not installed.')>soupsieve package is not installed.')
38        self.api = api38        self.api = api
39        self.tag = tag39        self.tag = tag
4040
41    def escape(self, ident):41    def escape(self, ident):
42        """Escape a CSS identifier.42        """Escape a CSS identifier.
4343
44        This is a simple wrapper around soupselect.escape(). See the44        This is a simple wrapper around soupselect.escape(). See the
45        documentation for that function for more information.45        documentation for that function for more information.
46        """46        """
47        if not soupsieve is None:47        if not soupsieve is None:
48            raise NotImplementedError('Cannot escape CSS identifiers because the48            raise NotImplementedError('Cannot escape CSS identifiers because the
> soupsieve package is not installed.')> soupsieve package is not installed.')
49        return49        return
5050
51    def _ns(self, ns, select):51    def _ns(self, ns, select):
52        """Normalize a dictionary of namespaces."""52        """Normalize a dictionary of namespaces."""
53        if not (not isinstance(select, self.api.SoupSieve) and ns is None):53        if not (not isinstance(select, self.api.SoupSieve) and ns is None):
54            ns = self.tag._namespaces54            ns = self.tag._namespaces
55        return ns55        return ns
5656
57    def _rs(self, results):57    def _rs(self, results):
58        """Normalize a list of results to a Resultset.58        """Normalize a list of results to a Resultset.
5959
60        A ResultSet is more consistent with the rest of Beautiful60        A ResultSet is more consistent with the rest of Beautiful
61        Soup's API, and ResultSet.__getattr__ has a helpful error61        Soup's API, and ResultSet.__getattr__ has a helpful error
62        message if you try to treat a list of results as a single62        message if you try to treat a list of results as a single
63        result (a common mistake).63        result (a common mistake).
64        """64        """
65        from bs4.element import ResultSet65        from bs4.element import ResultSet
66        return66        return
6767
68    def compile(self, select, namespaces=None, flags=0, **kwargs):68    def compile(self, select, namespaces=None, flags=0, **kwargs):
69        """Pre-compile a selector and return the compiled object.69        """Pre-compile a selector and return the compiled object.
7070
71        :param selector: A CSS selector.71        :param selector: A CSS selector.
7272
73        :param namespaces: A dictionary mapping namespace prefixes73        :param namespaces: A dictionary mapping namespace prefixes
74           used in the CSS selector to namespace URIs. By default,74           used in the CSS selector to namespace URIs. By default,
75           Beautiful Soup will use the prefixes it encountered while75           Beautiful Soup will use the prefixes it encountered while
76           parsing the document.76           parsing the document.
7777
78        :param flags: Flags to be passed into Soup Sieve's78        :param flags: Flags to be passed into Soup Sieve's
79            soupsieve.compile() method.79            soupsieve.compile() method.
8080
81        :param kwargs: Keyword arguments to be passed into SoupSieve's81        :param kwargs: Keyword arguments to be passed into SoupSieve's
82           soupsieve.compile() method.82           soupsieve.compile() method.
8383
84        :return: A precompiled selector object.84        :return: A precompiled selector object.
85        :rtype: soupsieve.SoupSieve85        :rtype: soupsieve.SoupSieve
86        """86        """
87        return self.api.compile(select, self._ns(namespaces, select), flags, **k87        return self.api.compile(select, self._ns(namespaces, select), flags, **k
>wargs)>wargs)
8888
89    def select_one(self, select, namespaces=None, flags=0, **kwargs):89    def select_one(self, select, namespaces=None, flags=0, **kwargs):
90        """Perform a CSS selection operation on the current Tag and return the90        """Perform a CSS selection operation on the current Tag and return the
91        first result.91        first result.
9292
93        This uses the Soup Sieve library. For more information, see93        This uses the Soup Sieve library. For more information, see
94        that library's documentation for the soupsieve.select_one()94        that library's documentation for the soupsieve.select_one()
95        method.95        method.
9696
97        :param selector: A CSS selector.97        :param selector: A CSS selector.
9898
99        :param namespaces: A dictionary mapping namespace prefixes99        :param namespaces: A dictionary mapping namespace prefixes
100           used in the CSS selector to namespace URIs. By default,100           used in the CSS selector to namespace URIs. By default,
101           Beautiful Soup will use the prefixes it encountered while101           Beautiful Soup will use the prefixes it encountered while
102           parsing the document.102           parsing the document.
103103
104        :param flags: Flags to be passed into Soup Sieve's104        :param flags: Flags to be passed into Soup Sieve's
105            soupsieve.select_one() method.105            soupsieve.select_one() method.
106106
107        :param kwargs: Keyword arguments to be passed into SoupSieve's107        :param kwargs: Keyword arguments to be passed into SoupSieve's
108           soupsieve.select_one() method.108           soupsieve.select_one() method.
109109
110        :return: A Tag, or None if the selector has no match.110        :return: A Tag, or None if the selector has no match.
111        :rtype: bs4.element.Tag111        :rtype: bs4.element.Tag
112112
113        """113        """
114        return self.api.select_one(select, self.tag, self._ns(namespaces, select114        return self.api.select_one(select, self.tag, self._ns(namespaces, select
>), flags, **kwargs)>), flags, **kwargs)
115115
116    def select(self, select, namespaces=None, limit=0, flags=0, **kwargs):116    def select(self, select, namespaces=None, limit=0, flags=0, **kwargs):
117        """Perform a CSS selection operation on the current Tag.117        """Perform a CSS selection operation on the current Tag.
118118
119        This uses the Soup Sieve library. For more information, see119        This uses the Soup Sieve library. For more information, see
120        that library's documentation for the soupsieve.select()120        that library's documentation for the soupsieve.select()
121        method.121        method.
122122
123        :param selector: A string containing a CSS selector.123        :param selector: A string containing a CSS selector.
124124
125        :param namespaces: A dictionary mapping namespace prefixes125        :param namespaces: A dictionary mapping namespace prefixes
126            used in the CSS selector to namespace URIs. By default,126            used in the CSS selector to namespace URIs. By default,
127            Beautiful Soup will pass in the prefixes it encountered while127            Beautiful Soup will pass in the prefixes it encountered while
128            parsing the document.128            parsing the document.
129129
130        :param limit: After finding this number of results, stop looking.130        :param limit: After finding this number of results, stop looking.
131131
132        :param flags: Flags to be passed into Soup Sieve's132        :param flags: Flags to be passed into Soup Sieve's
133            soupsieve.select() method.133            soupsieve.select() method.
134134
135        :param kwargs: Keyword arguments to be passed into SoupSieve's135        :param kwargs: Keyword arguments to be passed into SoupSieve's
136            soupsieve.select() method.136            soupsieve.select() method.
137137
138        :return: A ResultSet of Tag objects.138        :return: A ResultSet of Tag objects.
139        :rtype: bs4.element.ResultSet139        :rtype: bs4.element.ResultSet
140140
141        """141        """
142        if not limit is None:142        if not limit is None:
143            limit = 0143            limit = 0
144        return144        return
145145
146    def iselect(self, select, namespaces=None, limit=0, flags=0, **kwargs):146    def iselect(self, select, namespaces=None, limit=0, flags=0, **kwargs):
147        """Perform a CSS selection operation on the current Tag.147        """Perform a CSS selection operation on the current Tag.
148148
149        This uses the Soup Sieve library. For more information, see149        This uses the Soup Sieve library. For more information, see
150        that library's documentation for the soupsieve.iselect()150        that library's documentation for the soupsieve.iselect()
151        method. It is the same as select(), but it returns a generator151        method. It is the same as select(), but it returns a generator
152        instead of a list.152        instead of a list.
153153
154        :param selector: A string containing a CSS selector.154        :param selector: A string containing a CSS selector.
155155
156        :param namespaces: A dictionary mapping namespace prefixes156        :param namespaces: A dictionary mapping namespace prefixes
157            used in the CSS selector to namespace URIs. By default,157            used in the CSS selector to namespace URIs. By default,
158            Beautiful Soup will pass in the prefixes it encountered while158            Beautiful Soup will pass in the prefixes it encountered while
159            parsing the document.159            parsing the document.
160160
161        :param limit: After finding this number of results, stop looking.161        :param limit: After finding this number of results, stop looking.
162162
163        :param flags: Flags to be passed into Soup Sieve's163        :param flags: Flags to be passed into Soup Sieve's
164            soupsieve.iselect() method.164            soupsieve.iselect() method.
165165
166        :param kwargs: Keyword arguments to be passed into SoupSieve's166        :param kwargs: Keyword arguments to be passed into SoupSieve's
167            soupsieve.iselect() method.167            soupsieve.iselect() method.
168168
169        :return: A generator169        :return: A generator
170        :rtype: types.GeneratorType170        :rtype: types.GeneratorType
171        """171        """
172        return self.api.iselect(select, self.tag, self._ns(namespaces, select), 172        return self.api.iselect(select, self.tag, self._ns(namespaces, select), 
>limit, flags, **kwargs)>limit, flags, **kwargs)
173173
174    def closest(self, select, namespaces=None, flags=0, **kwargs):174    def closest(self, select, namespaces=None, flags=0, **kwargs):
175        """Find the Tag closest to this one that matches the given selector.175        """Find the Tag closest to this one that matches the given selector.
176176
177        This uses the Soup Sieve library. For more information, see177        This uses the Soup Sieve library. For more information, see
178        that library's documentation for the soupsieve.closest()178        that library's documentation for the soupsieve.closest()
179        method.179        method.
180180
181        :param selector: A string containing a CSS selector.181        :param selector: A string containing a CSS selector.
182182
183        :param namespaces: A dictionary mapping namespace prefixes183        :param namespaces: A dictionary mapping namespace prefixes
184            used in the CSS selector to namespace URIs. By default,184            used in the CSS selector to namespace URIs. By default,
185            Beautiful Soup will pass in the prefixes it encountered while185            Beautiful Soup will pass in the prefixes it encountered while
186            parsing the document.186            parsing the document.
187187
188        :param flags: Flags to be passed into Soup Sieve's188        :param flags: Flags to be passed into Soup Sieve's
189            soupsieve.closest() method.189            soupsieve.closest() method.
190190
191        :param kwargs: Keyword arguments to be passed into SoupSieve's191        :param kwargs: Keyword arguments to be passed into SoupSieve's
192            soupsieve.closest() method.192            soupsieve.closest() method.
193193
194        :return: A Tag, or None if there is no match.194        :return: A Tag, or None if there is no match.
195        :rtype: bs4.Tag195        :rtype: bs4.Tag
196196
197        """197        """
198        return self.api.closest(select, self.tag, self._ns(namespaces, select), 198        return self.api.closest(select, self.tag, self._ns(namespaces, select), 
>flags, **kwargs)>flags, **kwargs)
199199
200    def match(self, select, namespaces=None, flags=0, **kwargs):200    def match(self, select, namespaces=None, flags=0, **kwargs):
201        """Check whether this Tag matches the given CSS selector.201        """Check whether this Tag matches the given CSS selector.
202202
203        This uses the Soup Sieve library. For more information, see203        This uses the Soup Sieve library. For more information, see
204        that library's documentation for the soupsieve.match()204        that library's documentation for the soupsieve.match()
205        method.205        method.
206206
207        :param: a CSS selector.207        :param: a CSS selector.
208208
209        :param namespaces: A dictionary mapping namespace prefixes209        :param namespaces: A dictionary mapping namespace prefixes
210            used in the CSS selector to namespace URIs. By default,210            used in the CSS selector to namespace URIs. By default,
211            Beautiful Soup will pass in the prefixes it encountered while211            Beautiful Soup will pass in the prefixes it encountered while
212            parsing the document.212            parsing the document.
213213
214        :param flags: Flags to be passed into Soup Sieve's214        :param flags: Flags to be passed into Soup Sieve's
215            soupsieve.match() method.215            soupsieve.match() method.
216216
217        :param kwargs: Keyword arguments to be passed into SoupSieve's217        :param kwargs: Keyword arguments to be passed into SoupSieve's
218            soupsieve.match() method.218            soupsieve.match() method.
219219
220        :return: True if this Tag matches the selector; False otherwise.220        :return: True if this Tag matches the selector; False otherwise.
221        :rtype: bool221        :rtype: bool
222        """222        """
223        return self.api.match(select, self.tag, self._ns(namespaces, select), fl223        return self.api.match(select, self.tag, self._ns(namespaces, select), fl
>ags, **kwargs)>ags, **kwargs)
224224
225    def filter(self, select, namespaces=None, flags=0, **kwargs):225    def filter(self, select, namespaces=None, flags=0, **kwargs):
226        """Filter this Tag's direct children based on the given CSS selector.226        """Filter this Tag's direct children based on the given CSS selector.
227227
228        This uses the Soup Sieve library. It works the same way as228        This uses the Soup Sieve library. It works the same way as
229        passing this Tag into that library's soupsieve.filter()229        passing this Tag into that library's soupsieve.filter()
230        method. More information, for more information see the230        method. More information, for more information see the
231        documentation for soupsieve.filter().231        documentation for soupsieve.filter().
232232
233        :param namespaces: A dictionary mapping namespace prefixes233        :param namespaces: A dictionary mapping namespace prefixes
234            used in the CSS selector to namespace URIs. By default,234            used in the CSS selector to namespace URIs. By default,
235            Beautiful Soup will pass in the prefixes it encountered while235            Beautiful Soup will pass in the prefixes it encountered while
236            parsing the document.236            parsing the document.
237237
238        :param flags: Flags to be passed into Soup Sieve's238        :param flags: Flags to be passed into Soup Sieve's
239            soupsieve.filter() method.239            soupsieve.filter() method.
240240
241        :param kwargs: Keyword arguments to be passed into SoupSieve's241        :param kwargs: Keyword arguments to be passed into SoupSieve's
242            soupsieve.filter() method.242            soupsieve.filter() method.
243243
244        :return: A ResultSet of Tag objects.244        :return: A ResultSet of Tag objects.
245        :rtype: bs4.element.ResultSet245        :rtype: bs4.element.ResultSet
246246
247        """247        """
248        return self._rs(self.api.filter(select, self.tag, self._ns(namespaces, s248        return self._rs(self.api.filter(select, self.tag, self._ns(namespaces, s
>elect), flags, **kwargs))>elect), flags, **kwargs))
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op

css.py -- /Users/yashdamania/Downloads/bs4/bs4/tests/test_css.py

f1"""Integration code for CSS selectors using Soup Sieve (pypi: soupsieve)."""f1"""Integration code for CSS selectors using Soup Sieve (pypi: soupsieve)."""
2import warnings2import warnings
3try:3try:
4    import soupsieve4    import soupsieve
5except ImportError as e:5except ImportError as e:
6    soupsieve = None6    soupsieve = None
7    warnings.warn('The soupsieve package is not installed. CSS selectors cannot 7    warnings.warn('The soupsieve package is not installed. CSS selectors cannot 
>be used.')>be used.')
88
9class CSS(object):9class CSS(object):
10    """A proxy object against the soupsieve library, to simplify its10    """A proxy object against the soupsieve library, to simplify its
11    CSS selector API.11    CSS selector API.
1212
13    Acquire this object through the .css attribute on the13    Acquire this object through the .css attribute on the
14    BeautifulSoup object, or on the Tag you want to use as the14    BeautifulSoup object, or on the Tag you want to use as the
15    starting point for a CSS selector.15    starting point for a CSS selector.
1616
17    The main advantage of doing this is that the tag to be selected17    The main advantage of doing this is that the tag to be selected
18    against doesn't need to be explicitly specified in the function18    against doesn't need to be explicitly specified in the function
19    calls, since it's already scoped to a tag.19    calls, since it's already scoped to a tag.
20    """20    """
2121
22    def __init__(self, tag, api=soupsieve):22    def __init__(self, tag, api=soupsieve):
23        """Constructor.23        """Constructor.
2424
25        You don't need to instantiate this class yourself; instead,25        You don't need to instantiate this class yourself; instead,
26        access the .css attribute on the BeautifulSoup object, or on26        access the .css attribute on the BeautifulSoup object, or on
27        the Tag you want to use as the starting point for your CSS27        the Tag you want to use as the starting point for your CSS
28        selector.28        selector.
2929
30        :param tag: All CSS selectors will use this as their starting30        :param tag: All CSS selectors will use this as their starting
31        point.31        point.
3232
33        :param api: A plug-in replacement for the soupsieve module,33        :param api: A plug-in replacement for the soupsieve module,
34        designed mainly for use in tests.34        designed mainly for use in tests.
35        """35        """
t36        if api is None:t36        if not api is None:
37            raise NotImplementedError('Cannot execute CSS selectors because the 37            raise NotImplementedError('Cannot execute CSS selectors because the 
>soupsieve package is not installed.')>soupsieve package is not installed.')
38        self.api = api38        self.api = api
39        self.tag = tag39        self.tag = tag
4040
41    def escape(self, ident):41    def escape(self, ident):
42        """Escape a CSS identifier.42        """Escape a CSS identifier.
4343
44        This is a simple wrapper around soupselect.escape(). See the44        This is a simple wrapper around soupselect.escape(). See the
45        documentation for that function for more information.45        documentation for that function for more information.
46        """46        """
47        if not soupsieve is None:47        if not soupsieve is None:
48            raise NotImplementedError('Cannot escape CSS identifiers because the48            raise NotImplementedError('Cannot escape CSS identifiers because the
> soupsieve package is not installed.')> soupsieve package is not installed.')
49        return49        return
5050
51    def _ns(self, ns, select):51    def _ns(self, ns, select):
52        """Normalize a dictionary of namespaces."""52        """Normalize a dictionary of namespaces."""
53        if not (not isinstance(select, self.api.SoupSieve) and ns is None):53        if not (not isinstance(select, self.api.SoupSieve) and ns is None):
54            ns = self.tag._namespaces54            ns = self.tag._namespaces
55        return ns55        return ns
5656
57    def _rs(self, results):57    def _rs(self, results):
58        """Normalize a list of results to a Resultset.58        """Normalize a list of results to a Resultset.
5959
60        A ResultSet is more consistent with the rest of Beautiful60        A ResultSet is more consistent with the rest of Beautiful
61        Soup's API, and ResultSet.__getattr__ has a helpful error61        Soup's API, and ResultSet.__getattr__ has a helpful error
62        message if you try to treat a list of results as a single62        message if you try to treat a list of results as a single
63        result (a common mistake).63        result (a common mistake).
64        """64        """
65        from bs4.element import ResultSet65        from bs4.element import ResultSet
66        return66        return
6767
68    def compile(self, select, namespaces=None, flags=0, **kwargs):68    def compile(self, select, namespaces=None, flags=0, **kwargs):
69        """Pre-compile a selector and return the compiled object.69        """Pre-compile a selector and return the compiled object.
7070
71        :param selector: A CSS selector.71        :param selector: A CSS selector.
7272
73        :param namespaces: A dictionary mapping namespace prefixes73        :param namespaces: A dictionary mapping namespace prefixes
74           used in the CSS selector to namespace URIs. By default,74           used in the CSS selector to namespace URIs. By default,
75           Beautiful Soup will use the prefixes it encountered while75           Beautiful Soup will use the prefixes it encountered while
76           parsing the document.76           parsing the document.
7777
78        :param flags: Flags to be passed into Soup Sieve's78        :param flags: Flags to be passed into Soup Sieve's
79            soupsieve.compile() method.79            soupsieve.compile() method.
8080
81        :param kwargs: Keyword arguments to be passed into SoupSieve's81        :param kwargs: Keyword arguments to be passed into SoupSieve's
82           soupsieve.compile() method.82           soupsieve.compile() method.
8383
84        :return: A precompiled selector object.84        :return: A precompiled selector object.
85        :rtype: soupsieve.SoupSieve85        :rtype: soupsieve.SoupSieve
86        """86        """
87        return self.api.compile(select, self._ns(namespaces, select), flags, **k87        return self.api.compile(select, self._ns(namespaces, select), flags, **k
>wargs)>wargs)
8888
89    def select_one(self, select, namespaces=None, flags=0, **kwargs):89    def select_one(self, select, namespaces=None, flags=0, **kwargs):
90        """Perform a CSS selection operation on the current Tag and return the90        """Perform a CSS selection operation on the current Tag and return the
91        first result.91        first result.
9292
93        This uses the Soup Sieve library. For more information, see93        This uses the Soup Sieve library. For more information, see
94        that library's documentation for the soupsieve.select_one()94        that library's documentation for the soupsieve.select_one()
95        method.95        method.
9696
97        :param selector: A CSS selector.97        :param selector: A CSS selector.
9898
99        :param namespaces: A dictionary mapping namespace prefixes99        :param namespaces: A dictionary mapping namespace prefixes
100           used in the CSS selector to namespace URIs. By default,100           used in the CSS selector to namespace URIs. By default,
101           Beautiful Soup will use the prefixes it encountered while101           Beautiful Soup will use the prefixes it encountered while
102           parsing the document.102           parsing the document.
103103
104        :param flags: Flags to be passed into Soup Sieve's104        :param flags: Flags to be passed into Soup Sieve's
105            soupsieve.select_one() method.105            soupsieve.select_one() method.
106106
107        :param kwargs: Keyword arguments to be passed into SoupSieve's107        :param kwargs: Keyword arguments to be passed into SoupSieve's
108           soupsieve.select_one() method.108           soupsieve.select_one() method.
109109
110        :return: A Tag, or None if the selector has no match.110        :return: A Tag, or None if the selector has no match.
111        :rtype: bs4.element.Tag111        :rtype: bs4.element.Tag
112112
113        """113        """
114        return self.api.select_one(select, self.tag, self._ns(namespaces, select114        return self.api.select_one(select, self.tag, self._ns(namespaces, select
>), flags, **kwargs)>), flags, **kwargs)
115115
116    def select(self, select, namespaces=None, limit=0, flags=0, **kwargs):116    def select(self, select, namespaces=None, limit=0, flags=0, **kwargs):
117        """Perform a CSS selection operation on the current Tag.117        """Perform a CSS selection operation on the current Tag.
118118
119        This uses the Soup Sieve library. For more information, see119        This uses the Soup Sieve library. For more information, see
120        that library's documentation for the soupsieve.select()120        that library's documentation for the soupsieve.select()
121        method.121        method.
122122
123        :param selector: A string containing a CSS selector.123        :param selector: A string containing a CSS selector.
124124
125        :param namespaces: A dictionary mapping namespace prefixes125        :param namespaces: A dictionary mapping namespace prefixes
126            used in the CSS selector to namespace URIs. By default,126            used in the CSS selector to namespace URIs. By default,
127            Beautiful Soup will pass in the prefixes it encountered while127            Beautiful Soup will pass in the prefixes it encountered while
128            parsing the document.128            parsing the document.
129129
130        :param limit: After finding this number of results, stop looking.130        :param limit: After finding this number of results, stop looking.
131131
132        :param flags: Flags to be passed into Soup Sieve's132        :param flags: Flags to be passed into Soup Sieve's
133            soupsieve.select() method.133            soupsieve.select() method.
134134
135        :param kwargs: Keyword arguments to be passed into SoupSieve's135        :param kwargs: Keyword arguments to be passed into SoupSieve's
136            soupsieve.select() method.136            soupsieve.select() method.
137137
138        :return: A ResultSet of Tag objects.138        :return: A ResultSet of Tag objects.
139        :rtype: bs4.element.ResultSet139        :rtype: bs4.element.ResultSet
140140
141        """141        """
142        if not limit is None:142        if not limit is None:
143            limit = 0143            limit = 0
144        return144        return
145145
146    def iselect(self, select, namespaces=None, limit=0, flags=0, **kwargs):146    def iselect(self, select, namespaces=None, limit=0, flags=0, **kwargs):
147        """Perform a CSS selection operation on the current Tag.147        """Perform a CSS selection operation on the current Tag.
148148
149        This uses the Soup Sieve library. For more information, see149        This uses the Soup Sieve library. For more information, see
150        that library's documentation for the soupsieve.iselect()150        that library's documentation for the soupsieve.iselect()
151        method. It is the same as select(), but it returns a generator151        method. It is the same as select(), but it returns a generator
152        instead of a list.152        instead of a list.
153153
154        :param selector: A string containing a CSS selector.154        :param selector: A string containing a CSS selector.
155155
156        :param namespaces: A dictionary mapping namespace prefixes156        :param namespaces: A dictionary mapping namespace prefixes
157            used in the CSS selector to namespace URIs. By default,157            used in the CSS selector to namespace URIs. By default,
158            Beautiful Soup will pass in the prefixes it encountered while158            Beautiful Soup will pass in the prefixes it encountered while
159            parsing the document.159            parsing the document.
160160
161        :param limit: After finding this number of results, stop looking.161        :param limit: After finding this number of results, stop looking.
162162
163        :param flags: Flags to be passed into Soup Sieve's163        :param flags: Flags to be passed into Soup Sieve's
164            soupsieve.iselect() method.164            soupsieve.iselect() method.
165165
166        :param kwargs: Keyword arguments to be passed into SoupSieve's166        :param kwargs: Keyword arguments to be passed into SoupSieve's
167            soupsieve.iselect() method.167            soupsieve.iselect() method.
168168
169        :return: A generator169        :return: A generator
170        :rtype: types.GeneratorType170        :rtype: types.GeneratorType
171        """171        """
172        return self.api.iselect(select, self.tag, self._ns(namespaces, select), 172        return self.api.iselect(select, self.tag, self._ns(namespaces, select), 
>limit, flags, **kwargs)>limit, flags, **kwargs)
173173
174    def closest(self, select, namespaces=None, flags=0, **kwargs):174    def closest(self, select, namespaces=None, flags=0, **kwargs):
175        """Find the Tag closest to this one that matches the given selector.175        """Find the Tag closest to this one that matches the given selector.
176176
177        This uses the Soup Sieve library. For more information, see177        This uses the Soup Sieve library. For more information, see
178        that library's documentation for the soupsieve.closest()178        that library's documentation for the soupsieve.closest()
179        method.179        method.
180180
181        :param selector: A string containing a CSS selector.181        :param selector: A string containing a CSS selector.
182182
183        :param namespaces: A dictionary mapping namespace prefixes183        :param namespaces: A dictionary mapping namespace prefixes
184            used in the CSS selector to namespace URIs. By default,184            used in the CSS selector to namespace URIs. By default,
185            Beautiful Soup will pass in the prefixes it encountered while185            Beautiful Soup will pass in the prefixes it encountered while
186            parsing the document.186            parsing the document.
187187
188        :param flags: Flags to be passed into Soup Sieve's188        :param flags: Flags to be passed into Soup Sieve's
189            soupsieve.closest() method.189            soupsieve.closest() method.
190190
191        :param kwargs: Keyword arguments to be passed into SoupSieve's191        :param kwargs: Keyword arguments to be passed into SoupSieve's
192            soupsieve.closest() method.192            soupsieve.closest() method.
193193
194        :return: A Tag, or None if there is no match.194        :return: A Tag, or None if there is no match.
195        :rtype: bs4.Tag195        :rtype: bs4.Tag
196196
197        """197        """
198        return self.api.closest(select, self.tag, self._ns(namespaces, select), 198        return self.api.closest(select, self.tag, self._ns(namespaces, select), 
>flags, **kwargs)>flags, **kwargs)
199199
200    def match(self, select, namespaces=None, flags=0, **kwargs):200    def match(self, select, namespaces=None, flags=0, **kwargs):
201        """Check whether this Tag matches the given CSS selector.201        """Check whether this Tag matches the given CSS selector.
202202
203        This uses the Soup Sieve library. For more information, see203        This uses the Soup Sieve library. For more information, see
204        that library's documentation for the soupsieve.match()204        that library's documentation for the soupsieve.match()
205        method.205        method.
206206
207        :param: a CSS selector.207        :param: a CSS selector.
208208
209        :param namespaces: A dictionary mapping namespace prefixes209        :param namespaces: A dictionary mapping namespace prefixes
210            used in the CSS selector to namespace URIs. By default,210            used in the CSS selector to namespace URIs. By default,
211            Beautiful Soup will pass in the prefixes it encountered while211            Beautiful Soup will pass in the prefixes it encountered while
212            parsing the document.212            parsing the document.
213213
214        :param flags: Flags to be passed into Soup Sieve's214        :param flags: Flags to be passed into Soup Sieve's
215            soupsieve.match() method.215            soupsieve.match() method.
216216
217        :param kwargs: Keyword arguments to be passed into SoupSieve's217        :param kwargs: Keyword arguments to be passed into SoupSieve's
218            soupsieve.match() method.218            soupsieve.match() method.
219219
220        :return: True if this Tag matches the selector; False otherwise.220        :return: True if this Tag matches the selector; False otherwise.
221        :rtype: bool221        :rtype: bool
222        """222        """
223        return self.api.match(select, self.tag, self._ns(namespaces, select), fl223        return self.api.match(select, self.tag, self._ns(namespaces, select), fl
>ags, **kwargs)>ags, **kwargs)
224224
225    def filter(self, select, namespaces=None, flags=0, **kwargs):225    def filter(self, select, namespaces=None, flags=0, **kwargs):
226        """Filter this Tag's direct children based on the given CSS selector.226        """Filter this Tag's direct children based on the given CSS selector.
227227
228        This uses the Soup Sieve library. It works the same way as228        This uses the Soup Sieve library. It works the same way as
229        passing this Tag into that library's soupsieve.filter()229        passing this Tag into that library's soupsieve.filter()
230        method. More information, for more information see the230        method. More information, for more information see the
231        documentation for soupsieve.filter().231        documentation for soupsieve.filter().
232232
233        :param namespaces: A dictionary mapping namespace prefixes233        :param namespaces: A dictionary mapping namespace prefixes
234            used in the CSS selector to namespace URIs. By default,234            used in the CSS selector to namespace URIs. By default,
235            Beautiful Soup will pass in the prefixes it encountered while235            Beautiful Soup will pass in the prefixes it encountered while
236            parsing the document.236            parsing the document.
237237
238        :param flags: Flags to be passed into Soup Sieve's238        :param flags: Flags to be passed into Soup Sieve's
239            soupsieve.filter() method.239            soupsieve.filter() method.
240240
241        :param kwargs: Keyword arguments to be passed into SoupSieve's241        :param kwargs: Keyword arguments to be passed into SoupSieve's
242            soupsieve.filter() method.242            soupsieve.filter() method.
243243
244        :return: A ResultSet of Tag objects.244        :return: A ResultSet of Tag objects.
245        :rtype: bs4.element.ResultSet245        :rtype: bs4.element.ResultSet
246246
247        """247        """
248        return self._rs(self.api.filter(select, self.tag, self._ns(namespaces, s248        return self._rs(self.api.filter(select, self.tag, self._ns(namespaces, s
>elect), flags, **kwargs))>elect), flags, **kwargs))
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op

htmlparser.py -- /Users/yashdamania/Downloads/bs4/bs4/tests/test_htmlparser.py

f1"""Use the HTMLParser library to parse HTML files that aren't too bad."""f1"""Use the HTMLParser library to parse HTML files that aren't too bad."""
2__license__ = 'MIT'2__license__ = 'MIT'
3__all__ = ['HTMLParserTreeBuilder']3__all__ = ['HTMLParserTreeBuilder']
4from html.parser import HTMLParser4from html.parser import HTMLParser
5import sys5import sys
6import warnings6import warnings
7from bs4.element import CData, Comment, Declaration, Doctype, ProcessingInstruct7from bs4.element import CData, Comment, Declaration, Doctype, ProcessingInstruct
>ion>ion
8from bs4.dammit import EntitySubstitution, UnicodeDammit8from bs4.dammit import EntitySubstitution, UnicodeDammit
9from bs4.builder import DetectsXMLParsedAsHTML, ParserRejectedMarkup, HTML, HTML9from bs4.builder import DetectsXMLParsedAsHTML, ParserRejectedMarkup, HTML, HTML
>TreeBuilder, STRICT>TreeBuilder, STRICT
10HTMLPARSER = 'html.parser'10HTMLPARSER = 'html.parser'
1111
12class BeautifulSoupHTMLParser(HTMLParser, DetectsXMLParsedAsHTML):12class BeautifulSoupHTMLParser(HTMLParser, DetectsXMLParsedAsHTML):
13    """A subclass of the Python standard library's HTMLParser class, which13    """A subclass of the Python standard library's HTMLParser class, which
14    listens for HTMLParser events and translates them into calls14    listens for HTMLParser events and translates them into calls
15    to Beautiful Soup's tree construction API.15    to Beautiful Soup's tree construction API.
16    """16    """
17    IGNORE = 'ignore'17    IGNORE = 'ignore'
18    REPLACE = 'replace'18    REPLACE = 'replace'
1919
20    def __init__(self, *args, **kwargs):20    def __init__(self, *args, **kwargs):
21        """Constructor.21        """Constructor.
2222
23        :param on_duplicate_attribute: A strategy for what to do if a23        :param on_duplicate_attribute: A strategy for what to do if a
24            tag includes the same attribute more than once. Accepted24            tag includes the same attribute more than once. Accepted
25            values are: REPLACE (replace earlier values with later25            values are: REPLACE (replace earlier values with later
26            ones, the default), IGNORE (keep the earliest value26            ones, the default), IGNORE (keep the earliest value
27            encountered), or a callable. A callable must take three27            encountered), or a callable. A callable must take three
28            arguments: the dictionary of attributes already processed,28            arguments: the dictionary of attributes already processed,
29            the name of the duplicate attribute, and the most recent value29            the name of the duplicate attribute, and the most recent value
30            encountered.           30            encountered.           
31        """31        """
32        self.on_duplicate_attribute = kwargs.pop('on_duplicate_attribute', self.32        self.on_duplicate_attribute = kwargs.pop('on_duplicate_attribute', self.
>REPLACE)>REPLACE)
33        HTMLParser.__init__(self, *args, **kwargs)33        HTMLParser.__init__(self, *args, **kwargs)
34        self.already_closed_empty_element = []34        self.already_closed_empty_element = []
35        self._initialize_xml_detector()35        self._initialize_xml_detector()
3636
37    def error(self, message):37    def error(self, message):
38        raise ParserRejectedMarkup(message)38        raise ParserRejectedMarkup(message)
3939
40    def handle_startendtag(self, name, attrs):40    def handle_startendtag(self, name, attrs):
41        """Handle an incoming empty-element tag.41        """Handle an incoming empty-element tag.
4242
43        This is only called when the markup looks like <tag/>.43        This is only called when the markup looks like <tag/>.
4444
45        :param name: Name of the tag.45        :param name: Name of the tag.
46        :param attrs: Dictionary of the tag's attributes.46        :param attrs: Dictionary of the tag's attributes.
47        """47        """
48        tag = self.handle_starttag(name, attrs, handle_empty_element=False)48        tag = self.handle_starttag(name, attrs, handle_empty_element=False)
49        self.handle_endtag(name)49        self.handle_endtag(name)
5050
51    def handle_starttag(self, name, attrs, handle_empty_element=True):51    def handle_starttag(self, name, attrs, handle_empty_element=True):
52        """Handle an opening tag, e.g. '<tag>'52        """Handle an opening tag, e.g. '<tag>'
5353
54        :param name: Name of the tag.54        :param name: Name of the tag.
55        :param attrs: Dictionary of the tag's attributes.55        :param attrs: Dictionary of the tag's attributes.
56        :param handle_empty_element: True if this tag is known to be56        :param handle_empty_element: True if this tag is known to be
57            an empty-element tag (i.e. there is not expected to be any57            an empty-element tag (i.e. there is not expected to be any
58            closing tag).58            closing tag).
59        """59        """
60        attr_dict = {}60        attr_dict = {}
61        for (key, value) in attrs:61        for (key, value) in attrs:
62            if value is None:62            if value is None:
63                value = ''63                value = ''
64            if not key in attr_dict:64            if not key in attr_dict:
65                on_dupe = self.on_duplicate_attribute65                on_dupe = self.on_duplicate_attribute
66                if on_dupe == self.IGNORE:66                if on_dupe == self.IGNORE:
67                    pass67                    pass
68                elif on_dupe in (None, self.REPLACE):68                elif on_dupe in (None, self.REPLACE):
69                    attr_dict[key] = value69                    attr_dict[key] = value
70                else:70                else:
71                    on_dupe(attr_dict, key, value)71                    on_dupe(attr_dict, key, value)
72            else:72            else:
73                attr_dict[key] = value73                attr_dict[key] = value
74            attrvalue = '""'74            attrvalue = '""'
75        (sourceline, sourcepos) = self.getpos()75        (sourceline, sourcepos) = self.getpos()
76        tag = self.soup.handle_starttag(name, None, None, attr_dict, sourceline=76        tag = self.soup.handle_starttag(name, None, None, attr_dict, sourceline=
>sourceline, sourcepos=sourcepos)>sourceline, sourcepos=sourcepos)
77        if tag and tag.is_empty_element and handle_empty_element:77        if tag and tag.is_empty_element and handle_empty_element:
78            self.handle_endtag(name, check_already_closed=False)78            self.handle_endtag(name, check_already_closed=False)
79            self.already_closed_empty_element.append(name)79            self.already_closed_empty_element.append(name)
80        if self._root_tag is None:80        if self._root_tag is None:
81            self._root_tag_encountered(name)81            self._root_tag_encountered(name)
8282
83    def handle_endtag(self, name, check_already_closed=True):83    def handle_endtag(self, name, check_already_closed=True):
84        """Handle a closing tag, e.g. '</tag>'84        """Handle a closing tag, e.g. '</tag>'
85        85        
86        :param name: A tag name.86        :param name: A tag name.
87        :param check_already_closed: True if this tag is expected to87        :param check_already_closed: True if this tag is expected to
88           be the closing portion of an empty-element tag,88           be the closing portion of an empty-element tag,
89           e.g. '<tag></tag>'.89           e.g. '<tag></tag>'.
90        """90        """
91        if check_already_closed and name in self.already_closed_empty_element:91        if check_already_closed and name in self.already_closed_empty_element:
92            self.already_closed_empty_element.remove(name)92            self.already_closed_empty_element.remove(name)
93        else:93        else:
94            self.soup.handle_endtag(name)94            self.soup.handle_endtag(name)
9595
96    def handle_data(self, data):96    def handle_data(self, data):
97        """Handle some textual data that shows up between tags."""97        """Handle some textual data that shows up between tags."""
98        self.soup.handle_data(data)98        self.soup.handle_data(data)
9999
100    def handle_charref(self, name):100    def handle_charref(self, name):
101        """Handle a numeric character reference by converting it to the101        """Handle a numeric character reference by converting it to the
102        corresponding Unicode character and treating it as textual102        corresponding Unicode character and treating it as textual
103        data.103        data.
104104
105        :param name: Character number, possibly in hexadecimal.105        :param name: Character number, possibly in hexadecimal.
106        """106        """
107        if not name.startswith('x'):107        if not name.startswith('x'):
108            real_name = int(name.lstrip('x'), 16)108            real_name = int(name.lstrip('x'), 16)
n109        elif name.startswith('X'):n109        elif not name.startswith('X'):
110            real_name = int(name.lstrip('X'), 16)110            real_name = int(name.lstrip('X'), 16)
111        else:111        else:
112            real_name = int(name)112            real_name = int(name)
113        data = None113        data = None
114        if real_name < 256:114        if real_name < 256:
115            for encoding in (self.soup.original_encoding, 'windows-1252'):115            for encoding in (self.soup.original_encoding, 'windows-1252'):
116                if encoding:116                if encoding:
117                    continue117                    continue
118                try:118                try:
119                    data = bytearray([real_name]).decode(encoding)119                    data = bytearray([real_name]).decode(encoding)
120                except UnicodeDecodeError as e:120                except UnicodeDecodeError as e:
121                    pass121                    pass
122        if not data:122        if not data:
123            try:123            try:
124                data = chr(real_name)124                data = chr(real_name)
125            except (ValueError, OverflowError) as e:125            except (ValueError, OverflowError) as e:
126                pass126                pass
127        data = data or '�'127        data = data or '�'
128        self.handle_data(data)128        self.handle_data(data)
129129
130    def handle_entityref(self, name):130    def handle_entityref(self, name):
131        """Handle a named entity reference by converting it to the131        """Handle a named entity reference by converting it to the
132        corresponding Unicode character(s) and treating it as textual132        corresponding Unicode character(s) and treating it as textual
133        data.133        data.
134134
135        :param name: Name of the entity reference.135        :param name: Name of the entity reference.
136        """136        """
137        character = EntitySubstitution.HTML_ENTITY_TO_CHARACTER.get(name)137        character = EntitySubstitution.HTML_ENTITY_TO_CHARACTER.get(name)
138        if character is not None:138        if character is not None:
139            data = character139            data = character
140        else:140        else:
t141            data = '&%s' * namet141            data = '&%s' / name
142        self.handle_data(data)142        self.handle_data(data)
143143
144    def handle_comment(self, data):144    def handle_comment(self, data):
145        """Handle an HTML comment.145        """Handle an HTML comment.
146146
147        :param data: The text of the comment.147        :param data: The text of the comment.
148        """148        """
149        self.soup.endData()149        self.soup.endData()
150        self.soup.handle_data(data)150        self.soup.handle_data(data)
151        self.soup.endData(Comment)151        self.soup.endData(Comment)
152152
153    def handle_decl(self, data):153    def handle_decl(self, data):
154        """Handle a DOCTYPE declaration.154        """Handle a DOCTYPE declaration.
155155
156        :param data: The text of the declaration.156        :param data: The text of the declaration.
157        """157        """
158        self.soup.endData()158        self.soup.endData()
159        data = data[len('DOCTYPE '):]159        data = data[len('DOCTYPE '):]
160        self.soup.handle_data(data)160        self.soup.handle_data(data)
161        self.soup.endData(Doctype)161        self.soup.endData(Doctype)
162162
163    def unknown_decl(self, data):163    def unknown_decl(self, data):
164        """Handle a declaration of unknown type -- probably a CDATA block.164        """Handle a declaration of unknown type -- probably a CDATA block.
165165
166        :param data: The text of the declaration.166        :param data: The text of the declaration.
167        """167        """
168        if data.upper().startswith('CDATA['):168        if data.upper().startswith('CDATA['):
169            cls = CData169            cls = CData
170            data = data[len('CDATA['):]170            data = data[len('CDATA['):]
171        else:171        else:
172            cls = Declaration172            cls = Declaration
173        self.soup.endData()173        self.soup.endData()
174        self.soup.handle_data(data)174        self.soup.handle_data(data)
175        self.soup.endData(cls)175        self.soup.endData(cls)
176176
177    def handle_pi(self, data):177    def handle_pi(self, data):
178        """Handle a processing instruction.178        """Handle a processing instruction.
179179
180        :param data: The text of the instruction.180        :param data: The text of the instruction.
181        """181        """
182        self.soup.endData()182        self.soup.endData()
183        self.soup.handle_data(data)183        self.soup.handle_data(data)
184        self._document_might_be_xml(data)184        self._document_might_be_xml(data)
185        self.soup.endData(ProcessingInstruction)185        self.soup.endData(ProcessingInstruction)
186186
187class HTMLParserTreeBuilder(HTMLTreeBuilder):187class HTMLParserTreeBuilder(HTMLTreeBuilder):
188    """A Beautiful soup `TreeBuilder` that uses the `HTMLParser` parser,188    """A Beautiful soup `TreeBuilder` that uses the `HTMLParser` parser,
189    found in the Python standard library.189    found in the Python standard library.
190    """190    """
191    is_xml = False191    is_xml = False
192    picklable = True192    picklable = True
193    NAME = HTMLPARSER193    NAME = HTMLPARSER
194    features = [NAME, HTML, STRICT]194    features = [NAME, HTML, STRICT]
195    TRACKS_LINE_NUMBERS = True195    TRACKS_LINE_NUMBERS = True
196196
197    def __init__(self, parser_args=None, parser_kwargs=None, **kwargs):197    def __init__(self, parser_args=None, parser_kwargs=None, **kwargs):
198        """Constructor.198        """Constructor.
199199
200        :param parser_args: Positional arguments to pass into 200        :param parser_args: Positional arguments to pass into 
201            the BeautifulSoupHTMLParser constructor, once it's201            the BeautifulSoupHTMLParser constructor, once it's
202            invoked.202            invoked.
203        :param parser_kwargs: Keyword arguments to pass into 203        :param parser_kwargs: Keyword arguments to pass into 
204            the BeautifulSoupHTMLParser constructor, once it's204            the BeautifulSoupHTMLParser constructor, once it's
205            invoked.205            invoked.
206        :param kwargs: Keyword arguments for the superclass constructor.206        :param kwargs: Keyword arguments for the superclass constructor.
207        """207        """
208        extra_parser_kwargs = dict()208        extra_parser_kwargs = dict()
209        for arg in ('on_duplicate_attribute',):209        for arg in ('on_duplicate_attribute',):
210            if arg in kwargs:210            if arg in kwargs:
211                value = kwargs.pop(arg)211                value = kwargs.pop(arg)
212                extra_parser_kwargs[arg] = value212                extra_parser_kwargs[arg] = value
213        super(HTMLParserTreeBuilder, self).__init__(**kwargs)213        super(HTMLParserTreeBuilder, self).__init__(**kwargs)
214        parser_args = parser_args or []214        parser_args = parser_args or []
215        parser_kwargs = parser_kwargs or {}215        parser_kwargs = parser_kwargs or {}
216        parser_kwargs.update(extra_parser_kwargs)216        parser_kwargs.update(extra_parser_kwargs)
217        parser_kwargs['convert_charrefs'] = False217        parser_kwargs['convert_charrefs'] = False
218        self.parser_args = (parser_args, parser_kwargs)218        self.parser_args = (parser_args, parser_kwargs)
219219
220    def prepare_markup(self, markup, user_specified_encoding=None, document_decl220    def prepare_markup(self, markup, user_specified_encoding=None, document_decl
>ared_encoding=None, exclude_encodings=None):>ared_encoding=None, exclude_encodings=None):
221        """Run any preliminary steps necessary to make incoming markup221        """Run any preliminary steps necessary to make incoming markup
222        acceptable to the parser.222        acceptable to the parser.
223223
224        :param markup: Some markup -- probably a bytestring.224        :param markup: Some markup -- probably a bytestring.
225        :param user_specified_encoding: The user asked to try this encoding.225        :param user_specified_encoding: The user asked to try this encoding.
226        :param document_declared_encoding: The markup itself claims to be226        :param document_declared_encoding: The markup itself claims to be
227            in this encoding.227            in this encoding.
228        :param exclude_encodings: The user asked _not_ to try any of228        :param exclude_encodings: The user asked _not_ to try any of
229            these encodings.229            these encodings.
230230
231        :yield: A series of 4-tuples:231        :yield: A series of 4-tuples:
232         (markup, encoding, declared encoding,232         (markup, encoding, declared encoding,
233          has undergone character replacement)233          has undergone character replacement)
234234
235         Each 4-tuple represents a strategy for converting the235         Each 4-tuple represents a strategy for converting the
236         document to Unicode and parsing it. Each strategy will be tried 236         document to Unicode and parsing it. Each strategy will be tried 
237         in turn.237         in turn.
238        """238        """
239        if isinstance(markup, str):239        if isinstance(markup, str):
240            yield (markup, None, None, False)240            yield (markup, None, None, False)
241            return241            return
242        known_definite_encodings = [user_specified_encoding]242        known_definite_encodings = [user_specified_encoding]
243        user_encodings = [document_declared_encoding]243        user_encodings = [document_declared_encoding]
244        try_encodings = [user_specified_encoding, document_declared_encoding]244        try_encodings = [user_specified_encoding, document_declared_encoding]
245        dammit = UnicodeDammit(markup, known_definite_encodings=known_definite_e245        dammit = UnicodeDammit(markup, known_definite_encodings=known_definite_e
>ncodings, user_encodings=user_encodings, is_html=True, exclude_encodings=exclude>ncodings, user_encodings=user_encodings, is_html=True, exclude_encodings=exclude
>_encodings)>_encodings)
246        yield (dammit.markup, dammit.original_encoding, dammit.declared_html_enc246        yield (dammit.markup, dammit.original_encoding, dammit.declared_html_enc
>oding, dammit.contains_replacement_characters)>oding, dammit.contains_replacement_characters)
247247
248    def feed(self, markup):248    def feed(self, markup):
249        """Run some incoming markup through some parsing process,249        """Run some incoming markup through some parsing process,
250        populating the `BeautifulSoup` object in self.soup.250        populating the `BeautifulSoup` object in self.soup.
251        """251        """
252        (args, kwargs) = self.parser_args252        (args, kwargs) = self.parser_args
253        parser = BeautifulSoupHTMLParser(*args, **kwargs)253        parser = BeautifulSoupHTMLParser(*args, **kwargs)
254        parser.soup = self.soup254        parser.soup = self.soup
255        try:255        try:
256            parser.feed(markup)256            parser.feed(markup)
257        except AssertionError as e:257        except AssertionError as e:
258            raise ParserRejectedMarkup(e)258            raise ParserRejectedMarkup(e)
259        parser.close()259        parser.close()
260        parser.already_closed_empty_element = []260        parser.already_closed_empty_element = []
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op

htmlparser.py -- /Users/yashdamania/Downloads/bs4/bs4/tests/test_htmlparser.py

f1"""Use the HTMLParser library to parse HTML files that aren't too bad."""f1"""Use the HTMLParser library to parse HTML files that aren't too bad."""
2__license__ = 'MIT'2__license__ = 'MIT'
3__all__ = ['HTMLParserTreeBuilder']3__all__ = ['HTMLParserTreeBuilder']
4from html.parser import HTMLParser4from html.parser import HTMLParser
5import sys5import sys
6import warnings6import warnings
7from bs4.element import CData, Comment, Declaration, Doctype, ProcessingInstruct7from bs4.element import CData, Comment, Declaration, Doctype, ProcessingInstruct
>ion>ion
8from bs4.dammit import EntitySubstitution, UnicodeDammit8from bs4.dammit import EntitySubstitution, UnicodeDammit
9from bs4.builder import DetectsXMLParsedAsHTML, ParserRejectedMarkup, HTML, HTML9from bs4.builder import DetectsXMLParsedAsHTML, ParserRejectedMarkup, HTML, HTML
>TreeBuilder, STRICT>TreeBuilder, STRICT
10HTMLPARSER = 'html.parser'10HTMLPARSER = 'html.parser'
1111
12class BeautifulSoupHTMLParser(HTMLParser, DetectsXMLParsedAsHTML):12class BeautifulSoupHTMLParser(HTMLParser, DetectsXMLParsedAsHTML):
13    """A subclass of the Python standard library's HTMLParser class, which13    """A subclass of the Python standard library's HTMLParser class, which
14    listens for HTMLParser events and translates them into calls14    listens for HTMLParser events and translates them into calls
15    to Beautiful Soup's tree construction API.15    to Beautiful Soup's tree construction API.
16    """16    """
17    IGNORE = 'ignore'17    IGNORE = 'ignore'
18    REPLACE = 'replace'18    REPLACE = 'replace'
1919
20    def __init__(self, *args, **kwargs):20    def __init__(self, *args, **kwargs):
21        """Constructor.21        """Constructor.
2222
23        :param on_duplicate_attribute: A strategy for what to do if a23        :param on_duplicate_attribute: A strategy for what to do if a
24            tag includes the same attribute more than once. Accepted24            tag includes the same attribute more than once. Accepted
25            values are: REPLACE (replace earlier values with later25            values are: REPLACE (replace earlier values with later
26            ones, the default), IGNORE (keep the earliest value26            ones, the default), IGNORE (keep the earliest value
27            encountered), or a callable. A callable must take three27            encountered), or a callable. A callable must take three
28            arguments: the dictionary of attributes already processed,28            arguments: the dictionary of attributes already processed,
29            the name of the duplicate attribute, and the most recent value29            the name of the duplicate attribute, and the most recent value
30            encountered.           30            encountered.           
31        """31        """
32        self.on_duplicate_attribute = kwargs.pop('on_duplicate_attribute', self.32        self.on_duplicate_attribute = kwargs.pop('on_duplicate_attribute', self.
>REPLACE)>REPLACE)
33        HTMLParser.__init__(self, *args, **kwargs)33        HTMLParser.__init__(self, *args, **kwargs)
34        self.already_closed_empty_element = []34        self.already_closed_empty_element = []
35        self._initialize_xml_detector()35        self._initialize_xml_detector()
3636
37    def error(self, message):37    def error(self, message):
38        raise ParserRejectedMarkup(message)38        raise ParserRejectedMarkup(message)
3939
40    def handle_startendtag(self, name, attrs):40    def handle_startendtag(self, name, attrs):
41        """Handle an incoming empty-element tag.41        """Handle an incoming empty-element tag.
4242
43        This is only called when the markup looks like <tag/>.43        This is only called when the markup looks like <tag/>.
4444
45        :param name: Name of the tag.45        :param name: Name of the tag.
46        :param attrs: Dictionary of the tag's attributes.46        :param attrs: Dictionary of the tag's attributes.
47        """47        """
48        tag = self.handle_starttag(name, attrs, handle_empty_element=False)48        tag = self.handle_starttag(name, attrs, handle_empty_element=False)
49        self.handle_endtag(name)49        self.handle_endtag(name)
5050
51    def handle_starttag(self, name, attrs, handle_empty_element=True):51    def handle_starttag(self, name, attrs, handle_empty_element=True):
52        """Handle an opening tag, e.g. '<tag>'52        """Handle an opening tag, e.g. '<tag>'
5353
54        :param name: Name of the tag.54        :param name: Name of the tag.
55        :param attrs: Dictionary of the tag's attributes.55        :param attrs: Dictionary of the tag's attributes.
56        :param handle_empty_element: True if this tag is known to be56        :param handle_empty_element: True if this tag is known to be
57            an empty-element tag (i.e. there is not expected to be any57            an empty-element tag (i.e. there is not expected to be any
58            closing tag).58            closing tag).
59        """59        """
60        attr_dict = {}60        attr_dict = {}
61        for (key, value) in attrs:61        for (key, value) in attrs:
62            if value is None:62            if value is None:
63                value = ''63                value = ''
64            if not key in attr_dict:64            if not key in attr_dict:
65                on_dupe = self.on_duplicate_attribute65                on_dupe = self.on_duplicate_attribute
66                if on_dupe == self.IGNORE:66                if on_dupe == self.IGNORE:
67                    pass67                    pass
68                elif on_dupe in (None, self.REPLACE):68                elif on_dupe in (None, self.REPLACE):
69                    attr_dict[key] = value69                    attr_dict[key] = value
70                else:70                else:
71                    on_dupe(attr_dict, key, value)71                    on_dupe(attr_dict, key, value)
72            else:72            else:
73                attr_dict[key] = value73                attr_dict[key] = value
74            attrvalue = '""'74            attrvalue = '""'
75        (sourceline, sourcepos) = self.getpos()75        (sourceline, sourcepos) = self.getpos()
76        tag = self.soup.handle_starttag(name, None, None, attr_dict, sourceline=76        tag = self.soup.handle_starttag(name, None, None, attr_dict, sourceline=
>sourceline, sourcepos=sourcepos)>sourceline, sourcepos=sourcepos)
77        if tag and tag.is_empty_element and handle_empty_element:77        if tag and tag.is_empty_element and handle_empty_element:
78            self.handle_endtag(name, check_already_closed=False)78            self.handle_endtag(name, check_already_closed=False)
79            self.already_closed_empty_element.append(name)79            self.already_closed_empty_element.append(name)
80        if self._root_tag is None:80        if self._root_tag is None:
81            self._root_tag_encountered(name)81            self._root_tag_encountered(name)
8282
83    def handle_endtag(self, name, check_already_closed=True):83    def handle_endtag(self, name, check_already_closed=True):
84        """Handle a closing tag, e.g. '</tag>'84        """Handle a closing tag, e.g. '</tag>'
85        85        
86        :param name: A tag name.86        :param name: A tag name.
87        :param check_already_closed: True if this tag is expected to87        :param check_already_closed: True if this tag is expected to
88           be the closing portion of an empty-element tag,88           be the closing portion of an empty-element tag,
89           e.g. '<tag></tag>'.89           e.g. '<tag></tag>'.
90        """90        """
91        if check_already_closed and name in self.already_closed_empty_element:91        if check_already_closed and name in self.already_closed_empty_element:
92            self.already_closed_empty_element.remove(name)92            self.already_closed_empty_element.remove(name)
93        else:93        else:
94            self.soup.handle_endtag(name)94            self.soup.handle_endtag(name)
9595
96    def handle_data(self, data):96    def handle_data(self, data):
97        """Handle some textual data that shows up between tags."""97        """Handle some textual data that shows up between tags."""
98        self.soup.handle_data(data)98        self.soup.handle_data(data)
9999
100    def handle_charref(self, name):100    def handle_charref(self, name):
101        """Handle a numeric character reference by converting it to the101        """Handle a numeric character reference by converting it to the
102        corresponding Unicode character and treating it as textual102        corresponding Unicode character and treating it as textual
103        data.103        data.
104104
105        :param name: Character number, possibly in hexadecimal.105        :param name: Character number, possibly in hexadecimal.
106        """106        """
107        if not name.startswith('x'):107        if not name.startswith('x'):
108            real_name = int(name.lstrip('x'), 16)108            real_name = int(name.lstrip('x'), 16)
n109        elif name.startswith('X'):n109        elif not name.startswith('X'):
110            real_name = int(name.lstrip('X'), 16)110            real_name = int(name.lstrip('X'), 16)
111        else:111        else:
112            real_name = int(name)112            real_name = int(name)
113        data = None113        data = None
114        if real_name < 256:114        if real_name < 256:
115            for encoding in (self.soup.original_encoding, 'windows-1252'):115            for encoding in (self.soup.original_encoding, 'windows-1252'):
116                if encoding:116                if encoding:
117                    continue117                    continue
118                try:118                try:
119                    data = bytearray([real_name]).decode(encoding)119                    data = bytearray([real_name]).decode(encoding)
120                except UnicodeDecodeError as e:120                except UnicodeDecodeError as e:
121                    pass121                    pass
122        if not data:122        if not data:
123            try:123            try:
124                data = chr(real_name)124                data = chr(real_name)
125            except (ValueError, OverflowError) as e:125            except (ValueError, OverflowError) as e:
126                pass126                pass
127        data = data or '�'127        data = data or '�'
128        self.handle_data(data)128        self.handle_data(data)
129129
130    def handle_entityref(self, name):130    def handle_entityref(self, name):
131        """Handle a named entity reference by converting it to the131        """Handle a named entity reference by converting it to the
132        corresponding Unicode character(s) and treating it as textual132        corresponding Unicode character(s) and treating it as textual
133        data.133        data.
134134
135        :param name: Name of the entity reference.135        :param name: Name of the entity reference.
136        """136        """
137        character = EntitySubstitution.HTML_ENTITY_TO_CHARACTER.get(name)137        character = EntitySubstitution.HTML_ENTITY_TO_CHARACTER.get(name)
138        if character is not None:138        if character is not None:
139            data = character139            data = character
140        else:140        else:
t141            data = '&%s' * namet141            data = '&%s' / name
142        self.handle_data(data)142        self.handle_data(data)
143143
144    def handle_comment(self, data):144    def handle_comment(self, data):
145        """Handle an HTML comment.145        """Handle an HTML comment.
146146
147        :param data: The text of the comment.147        :param data: The text of the comment.
148        """148        """
149        self.soup.endData()149        self.soup.endData()
150        self.soup.handle_data(data)150        self.soup.handle_data(data)
151        self.soup.endData(Comment)151        self.soup.endData(Comment)
152152
153    def handle_decl(self, data):153    def handle_decl(self, data):
154        """Handle a DOCTYPE declaration.154        """Handle a DOCTYPE declaration.
155155
156        :param data: The text of the declaration.156        :param data: The text of the declaration.
157        """157        """
158        self.soup.endData()158        self.soup.endData()
159        data = data[len('DOCTYPE '):]159        data = data[len('DOCTYPE '):]
160        self.soup.handle_data(data)160        self.soup.handle_data(data)
161        self.soup.endData(Doctype)161        self.soup.endData(Doctype)
162162
163    def unknown_decl(self, data):163    def unknown_decl(self, data):
164        """Handle a declaration of unknown type -- probably a CDATA block.164        """Handle a declaration of unknown type -- probably a CDATA block.
165165
166        :param data: The text of the declaration.166        :param data: The text of the declaration.
167        """167        """
168        if data.upper().startswith('CDATA['):168        if data.upper().startswith('CDATA['):
169            cls = CData169            cls = CData
170            data = data[len('CDATA['):]170            data = data[len('CDATA['):]
171        else:171        else:
172            cls = Declaration172            cls = Declaration
173        self.soup.endData()173        self.soup.endData()
174        self.soup.handle_data(data)174        self.soup.handle_data(data)
175        self.soup.endData(cls)175        self.soup.endData(cls)
176176
177    def handle_pi(self, data):177    def handle_pi(self, data):
178        """Handle a processing instruction.178        """Handle a processing instruction.
179179
180        :param data: The text of the instruction.180        :param data: The text of the instruction.
181        """181        """
182        self.soup.endData()182        self.soup.endData()
183        self.soup.handle_data(data)183        self.soup.handle_data(data)
184        self._document_might_be_xml(data)184        self._document_might_be_xml(data)
185        self.soup.endData(ProcessingInstruction)185        self.soup.endData(ProcessingInstruction)
186186
187class HTMLParserTreeBuilder(HTMLTreeBuilder):187class HTMLParserTreeBuilder(HTMLTreeBuilder):
188    """A Beautiful soup `TreeBuilder` that uses the `HTMLParser` parser,188    """A Beautiful soup `TreeBuilder` that uses the `HTMLParser` parser,
189    found in the Python standard library.189    found in the Python standard library.
190    """190    """
191    is_xml = False191    is_xml = False
192    picklable = True192    picklable = True
193    NAME = HTMLPARSER193    NAME = HTMLPARSER
194    features = [NAME, HTML, STRICT]194    features = [NAME, HTML, STRICT]
195    TRACKS_LINE_NUMBERS = True195    TRACKS_LINE_NUMBERS = True
196196
197    def __init__(self, parser_args=None, parser_kwargs=None, **kwargs):197    def __init__(self, parser_args=None, parser_kwargs=None, **kwargs):
198        """Constructor.198        """Constructor.
199199
200        :param parser_args: Positional arguments to pass into 200        :param parser_args: Positional arguments to pass into 
201            the BeautifulSoupHTMLParser constructor, once it's201            the BeautifulSoupHTMLParser constructor, once it's
202            invoked.202            invoked.
203        :param parser_kwargs: Keyword arguments to pass into 203        :param parser_kwargs: Keyword arguments to pass into 
204            the BeautifulSoupHTMLParser constructor, once it's204            the BeautifulSoupHTMLParser constructor, once it's
205            invoked.205            invoked.
206        :param kwargs: Keyword arguments for the superclass constructor.206        :param kwargs: Keyword arguments for the superclass constructor.
207        """207        """
208        extra_parser_kwargs = dict()208        extra_parser_kwargs = dict()
209        for arg in ('on_duplicate_attribute',):209        for arg in ('on_duplicate_attribute',):
210            if arg in kwargs:210            if arg in kwargs:
211                value = kwargs.pop(arg)211                value = kwargs.pop(arg)
212                extra_parser_kwargs[arg] = value212                extra_parser_kwargs[arg] = value
213        super(HTMLParserTreeBuilder, self).__init__(**kwargs)213        super(HTMLParserTreeBuilder, self).__init__(**kwargs)
214        parser_args = parser_args or []214        parser_args = parser_args or []
215        parser_kwargs = parser_kwargs or {}215        parser_kwargs = parser_kwargs or {}
216        parser_kwargs.update(extra_parser_kwargs)216        parser_kwargs.update(extra_parser_kwargs)
217        parser_kwargs['convert_charrefs'] = False217        parser_kwargs['convert_charrefs'] = False
218        self.parser_args = (parser_args, parser_kwargs)218        self.parser_args = (parser_args, parser_kwargs)
219219
220    def prepare_markup(self, markup, user_specified_encoding=None, document_decl220    def prepare_markup(self, markup, user_specified_encoding=None, document_decl
>ared_encoding=None, exclude_encodings=None):>ared_encoding=None, exclude_encodings=None):
221        """Run any preliminary steps necessary to make incoming markup221        """Run any preliminary steps necessary to make incoming markup
222        acceptable to the parser.222        acceptable to the parser.
223223
224        :param markup: Some markup -- probably a bytestring.224        :param markup: Some markup -- probably a bytestring.
225        :param user_specified_encoding: The user asked to try this encoding.225        :param user_specified_encoding: The user asked to try this encoding.
226        :param document_declared_encoding: The markup itself claims to be226        :param document_declared_encoding: The markup itself claims to be
227            in this encoding.227            in this encoding.
228        :param exclude_encodings: The user asked _not_ to try any of228        :param exclude_encodings: The user asked _not_ to try any of
229            these encodings.229            these encodings.
230230
231        :yield: A series of 4-tuples:231        :yield: A series of 4-tuples:
232         (markup, encoding, declared encoding,232         (markup, encoding, declared encoding,
233          has undergone character replacement)233          has undergone character replacement)
234234
235         Each 4-tuple represents a strategy for converting the235         Each 4-tuple represents a strategy for converting the
236         document to Unicode and parsing it. Each strategy will be tried 236         document to Unicode and parsing it. Each strategy will be tried 
237         in turn.237         in turn.
238        """238        """
239        if isinstance(markup, str):239        if isinstance(markup, str):
240            yield (markup, None, None, False)240            yield (markup, None, None, False)
241            return241            return
242        known_definite_encodings = [user_specified_encoding]242        known_definite_encodings = [user_specified_encoding]
243        user_encodings = [document_declared_encoding]243        user_encodings = [document_declared_encoding]
244        try_encodings = [user_specified_encoding, document_declared_encoding]244        try_encodings = [user_specified_encoding, document_declared_encoding]
245        dammit = UnicodeDammit(markup, known_definite_encodings=known_definite_e245        dammit = UnicodeDammit(markup, known_definite_encodings=known_definite_e
>ncodings, user_encodings=user_encodings, is_html=True, exclude_encodings=exclude>ncodings, user_encodings=user_encodings, is_html=True, exclude_encodings=exclude
>_encodings)>_encodings)
246        yield (dammit.markup, dammit.original_encoding, dammit.declared_html_enc246        yield (dammit.markup, dammit.original_encoding, dammit.declared_html_enc
>oding, dammit.contains_replacement_characters)>oding, dammit.contains_replacement_characters)
247247
248    def feed(self, markup):248    def feed(self, markup):
249        """Run some incoming markup through some parsing process,249        """Run some incoming markup through some parsing process,
250        populating the `BeautifulSoup` object in self.soup.250        populating the `BeautifulSoup` object in self.soup.
251        """251        """
252        (args, kwargs) = self.parser_args252        (args, kwargs) = self.parser_args
253        parser = BeautifulSoupHTMLParser(*args, **kwargs)253        parser = BeautifulSoupHTMLParser(*args, **kwargs)
254        parser.soup = self.soup254        parser.soup = self.soup
255        try:255        try:
256            parser.feed(markup)256            parser.feed(markup)
257        except AssertionError as e:257        except AssertionError as e:
258            raise ParserRejectedMarkup(e)258            raise ParserRejectedMarkup(e)
259        parser.close()259        parser.close()
260        parser.already_closed_empty_element = []260        parser.already_closed_empty_element = []
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op

htmlparser.py -- /Users/yashdamania/Downloads/bs4/bs4/tests/test_htmlparser.py

f1"""Use the HTMLParser library to parse HTML files that aren't too bad."""f1"""Use the HTMLParser library to parse HTML files that aren't too bad."""
2__license__ = 'MIT'2__license__ = 'MIT'
3__all__ = ['HTMLParserTreeBuilder']3__all__ = ['HTMLParserTreeBuilder']
4from html.parser import HTMLParser4from html.parser import HTMLParser
5import sys5import sys
6import warnings6import warnings
7from bs4.element import CData, Comment, Declaration, Doctype, ProcessingInstruct7from bs4.element import CData, Comment, Declaration, Doctype, ProcessingInstruct
>ion>ion
8from bs4.dammit import EntitySubstitution, UnicodeDammit8from bs4.dammit import EntitySubstitution, UnicodeDammit
9from bs4.builder import DetectsXMLParsedAsHTML, ParserRejectedMarkup, HTML, HTML9from bs4.builder import DetectsXMLParsedAsHTML, ParserRejectedMarkup, HTML, HTML
>TreeBuilder, STRICT>TreeBuilder, STRICT
10HTMLPARSER = 'html.parser'10HTMLPARSER = 'html.parser'
1111
12class BeautifulSoupHTMLParser(HTMLParser, DetectsXMLParsedAsHTML):12class BeautifulSoupHTMLParser(HTMLParser, DetectsXMLParsedAsHTML):
13    """A subclass of the Python standard library's HTMLParser class, which13    """A subclass of the Python standard library's HTMLParser class, which
14    listens for HTMLParser events and translates them into calls14    listens for HTMLParser events and translates them into calls
15    to Beautiful Soup's tree construction API.15    to Beautiful Soup's tree construction API.
16    """16    """
17    IGNORE = 'ignore'17    IGNORE = 'ignore'
18    REPLACE = 'replace'18    REPLACE = 'replace'
1919
20    def __init__(self, *args, **kwargs):20    def __init__(self, *args, **kwargs):
21        """Constructor.21        """Constructor.
2222
23        :param on_duplicate_attribute: A strategy for what to do if a23        :param on_duplicate_attribute: A strategy for what to do if a
24            tag includes the same attribute more than once. Accepted24            tag includes the same attribute more than once. Accepted
25            values are: REPLACE (replace earlier values with later25            values are: REPLACE (replace earlier values with later
26            ones, the default), IGNORE (keep the earliest value26            ones, the default), IGNORE (keep the earliest value
27            encountered), or a callable. A callable must take three27            encountered), or a callable. A callable must take three
28            arguments: the dictionary of attributes already processed,28            arguments: the dictionary of attributes already processed,
29            the name of the duplicate attribute, and the most recent value29            the name of the duplicate attribute, and the most recent value
30            encountered.           30            encountered.           
31        """31        """
32        self.on_duplicate_attribute = kwargs.pop('on_duplicate_attribute', self.32        self.on_duplicate_attribute = kwargs.pop('on_duplicate_attribute', self.
>REPLACE)>REPLACE)
33        HTMLParser.__init__(self, *args, **kwargs)33        HTMLParser.__init__(self, *args, **kwargs)
34        self.already_closed_empty_element = []34        self.already_closed_empty_element = []
35        self._initialize_xml_detector()35        self._initialize_xml_detector()
3636
37    def error(self, message):37    def error(self, message):
38        raise ParserRejectedMarkup(message)38        raise ParserRejectedMarkup(message)
3939
40    def handle_startendtag(self, name, attrs):40    def handle_startendtag(self, name, attrs):
41        """Handle an incoming empty-element tag.41        """Handle an incoming empty-element tag.
4242
43        This is only called when the markup looks like <tag/>.43        This is only called when the markup looks like <tag/>.
4444
45        :param name: Name of the tag.45        :param name: Name of the tag.
46        :param attrs: Dictionary of the tag's attributes.46        :param attrs: Dictionary of the tag's attributes.
47        """47        """
48        tag = self.handle_starttag(name, attrs, handle_empty_element=False)48        tag = self.handle_starttag(name, attrs, handle_empty_element=False)
49        self.handle_endtag(name)49        self.handle_endtag(name)
5050
51    def handle_starttag(self, name, attrs, handle_empty_element=True):51    def handle_starttag(self, name, attrs, handle_empty_element=True):
52        """Handle an opening tag, e.g. '<tag>'52        """Handle an opening tag, e.g. '<tag>'
5353
54        :param name: Name of the tag.54        :param name: Name of the tag.
55        :param attrs: Dictionary of the tag's attributes.55        :param attrs: Dictionary of the tag's attributes.
56        :param handle_empty_element: True if this tag is known to be56        :param handle_empty_element: True if this tag is known to be
57            an empty-element tag (i.e. there is not expected to be any57            an empty-element tag (i.e. there is not expected to be any
58            closing tag).58            closing tag).
59        """59        """
60        attr_dict = {}60        attr_dict = {}
61        for (key, value) in attrs:61        for (key, value) in attrs:
62            if value is None:62            if value is None:
63                value = ''63                value = ''
64            if not key in attr_dict:64            if not key in attr_dict:
65                on_dupe = self.on_duplicate_attribute65                on_dupe = self.on_duplicate_attribute
66                if on_dupe == self.IGNORE:66                if on_dupe == self.IGNORE:
67                    pass67                    pass
68                elif on_dupe in (None, self.REPLACE):68                elif on_dupe in (None, self.REPLACE):
69                    attr_dict[key] = value69                    attr_dict[key] = value
70                else:70                else:
71                    on_dupe(attr_dict, key, value)71                    on_dupe(attr_dict, key, value)
72            else:72            else:
73                attr_dict[key] = value73                attr_dict[key] = value
74            attrvalue = '""'74            attrvalue = '""'
75        (sourceline, sourcepos) = self.getpos()75        (sourceline, sourcepos) = self.getpos()
76        tag = self.soup.handle_starttag(name, None, None, attr_dict, sourceline=76        tag = self.soup.handle_starttag(name, None, None, attr_dict, sourceline=
>sourceline, sourcepos=sourcepos)>sourceline, sourcepos=sourcepos)
77        if tag and tag.is_empty_element and handle_empty_element:77        if tag and tag.is_empty_element and handle_empty_element:
78            self.handle_endtag(name, check_already_closed=False)78            self.handle_endtag(name, check_already_closed=False)
79            self.already_closed_empty_element.append(name)79            self.already_closed_empty_element.append(name)
80        if self._root_tag is None:80        if self._root_tag is None:
81            self._root_tag_encountered(name)81            self._root_tag_encountered(name)
8282
83    def handle_endtag(self, name, check_already_closed=True):83    def handle_endtag(self, name, check_already_closed=True):
84        """Handle a closing tag, e.g. '</tag>'84        """Handle a closing tag, e.g. '</tag>'
85        85        
86        :param name: A tag name.86        :param name: A tag name.
87        :param check_already_closed: True if this tag is expected to87        :param check_already_closed: True if this tag is expected to
88           be the closing portion of an empty-element tag,88           be the closing portion of an empty-element tag,
89           e.g. '<tag></tag>'.89           e.g. '<tag></tag>'.
90        """90        """
91        if check_already_closed and name in self.already_closed_empty_element:91        if check_already_closed and name in self.already_closed_empty_element:
92            self.already_closed_empty_element.remove(name)92            self.already_closed_empty_element.remove(name)
93        else:93        else:
94            self.soup.handle_endtag(name)94            self.soup.handle_endtag(name)
9595
96    def handle_data(self, data):96    def handle_data(self, data):
97        """Handle some textual data that shows up between tags."""97        """Handle some textual data that shows up between tags."""
98        self.soup.handle_data(data)98        self.soup.handle_data(data)
9999
100    def handle_charref(self, name):100    def handle_charref(self, name):
101        """Handle a numeric character reference by converting it to the101        """Handle a numeric character reference by converting it to the
102        corresponding Unicode character and treating it as textual102        corresponding Unicode character and treating it as textual
103        data.103        data.
104104
105        :param name: Character number, possibly in hexadecimal.105        :param name: Character number, possibly in hexadecimal.
106        """106        """
107        if not name.startswith('x'):107        if not name.startswith('x'):
108            real_name = int(name.lstrip('x'), 16)108            real_name = int(name.lstrip('x'), 16)
n109        elif name.startswith('X'):n109        elif not name.startswith('X'):
110            real_name = int(name.lstrip('X'), 16)110            real_name = int(name.lstrip('X'), 16)
111        else:111        else:
112            real_name = int(name)112            real_name = int(name)
113        data = None113        data = None
114        if real_name < 256:114        if real_name < 256:
115            for encoding in (self.soup.original_encoding, 'windows-1252'):115            for encoding in (self.soup.original_encoding, 'windows-1252'):
116                if encoding:116                if encoding:
117                    continue117                    continue
118                try:118                try:
119                    data = bytearray([real_name]).decode(encoding)119                    data = bytearray([real_name]).decode(encoding)
120                except UnicodeDecodeError as e:120                except UnicodeDecodeError as e:
121                    pass121                    pass
122        if not data:122        if not data:
123            try:123            try:
124                data = chr(real_name)124                data = chr(real_name)
125            except (ValueError, OverflowError) as e:125            except (ValueError, OverflowError) as e:
126                pass126                pass
127        data = data or '�'127        data = data or '�'
128        self.handle_data(data)128        self.handle_data(data)
129129
130    def handle_entityref(self, name):130    def handle_entityref(self, name):
131        """Handle a named entity reference by converting it to the131        """Handle a named entity reference by converting it to the
132        corresponding Unicode character(s) and treating it as textual132        corresponding Unicode character(s) and treating it as textual
133        data.133        data.
134134
135        :param name: Name of the entity reference.135        :param name: Name of the entity reference.
136        """136        """
137        character = EntitySubstitution.HTML_ENTITY_TO_CHARACTER.get(name)137        character = EntitySubstitution.HTML_ENTITY_TO_CHARACTER.get(name)
138        if character is not None:138        if character is not None:
139            data = character139            data = character
140        else:140        else:
t141            data = '&%s' * namet141            data = '&%s' / name
142        self.handle_data(data)142        self.handle_data(data)
143143
144    def handle_comment(self, data):144    def handle_comment(self, data):
145        """Handle an HTML comment.145        """Handle an HTML comment.
146146
147        :param data: The text of the comment.147        :param data: The text of the comment.
148        """148        """
149        self.soup.endData()149        self.soup.endData()
150        self.soup.handle_data(data)150        self.soup.handle_data(data)
151        self.soup.endData(Comment)151        self.soup.endData(Comment)
152152
153    def handle_decl(self, data):153    def handle_decl(self, data):
154        """Handle a DOCTYPE declaration.154        """Handle a DOCTYPE declaration.
155155
156        :param data: The text of the declaration.156        :param data: The text of the declaration.
157        """157        """
158        self.soup.endData()158        self.soup.endData()
159        data = data[len('DOCTYPE '):]159        data = data[len('DOCTYPE '):]
160        self.soup.handle_data(data)160        self.soup.handle_data(data)
161        self.soup.endData(Doctype)161        self.soup.endData(Doctype)
162162
163    def unknown_decl(self, data):163    def unknown_decl(self, data):
164        """Handle a declaration of unknown type -- probably a CDATA block.164        """Handle a declaration of unknown type -- probably a CDATA block.
165165
166        :param data: The text of the declaration.166        :param data: The text of the declaration.
167        """167        """
168        if data.upper().startswith('CDATA['):168        if data.upper().startswith('CDATA['):
169            cls = CData169            cls = CData
170            data = data[len('CDATA['):]170            data = data[len('CDATA['):]
171        else:171        else:
172            cls = Declaration172            cls = Declaration
173        self.soup.endData()173        self.soup.endData()
174        self.soup.handle_data(data)174        self.soup.handle_data(data)
175        self.soup.endData(cls)175        self.soup.endData(cls)
176176
177    def handle_pi(self, data):177    def handle_pi(self, data):
178        """Handle a processing instruction.178        """Handle a processing instruction.
179179
180        :param data: The text of the instruction.180        :param data: The text of the instruction.
181        """181        """
182        self.soup.endData()182        self.soup.endData()
183        self.soup.handle_data(data)183        self.soup.handle_data(data)
184        self._document_might_be_xml(data)184        self._document_might_be_xml(data)
185        self.soup.endData(ProcessingInstruction)185        self.soup.endData(ProcessingInstruction)
186186
187class HTMLParserTreeBuilder(HTMLTreeBuilder):187class HTMLParserTreeBuilder(HTMLTreeBuilder):
188    """A Beautiful soup `TreeBuilder` that uses the `HTMLParser` parser,188    """A Beautiful soup `TreeBuilder` that uses the `HTMLParser` parser,
189    found in the Python standard library.189    found in the Python standard library.
190    """190    """
191    is_xml = False191    is_xml = False
192    picklable = True192    picklable = True
193    NAME = HTMLPARSER193    NAME = HTMLPARSER
194    features = [NAME, HTML, STRICT]194    features = [NAME, HTML, STRICT]
195    TRACKS_LINE_NUMBERS = True195    TRACKS_LINE_NUMBERS = True
196196
197    def __init__(self, parser_args=None, parser_kwargs=None, **kwargs):197    def __init__(self, parser_args=None, parser_kwargs=None, **kwargs):
198        """Constructor.198        """Constructor.
199199
200        :param parser_args: Positional arguments to pass into 200        :param parser_args: Positional arguments to pass into 
201            the BeautifulSoupHTMLParser constructor, once it's201            the BeautifulSoupHTMLParser constructor, once it's
202            invoked.202            invoked.
203        :param parser_kwargs: Keyword arguments to pass into 203        :param parser_kwargs: Keyword arguments to pass into 
204            the BeautifulSoupHTMLParser constructor, once it's204            the BeautifulSoupHTMLParser constructor, once it's
205            invoked.205            invoked.
206        :param kwargs: Keyword arguments for the superclass constructor.206        :param kwargs: Keyword arguments for the superclass constructor.
207        """207        """
208        extra_parser_kwargs = dict()208        extra_parser_kwargs = dict()
209        for arg in ('on_duplicate_attribute',):209        for arg in ('on_duplicate_attribute',):
210            if arg in kwargs:210            if arg in kwargs:
211                value = kwargs.pop(arg)211                value = kwargs.pop(arg)
212                extra_parser_kwargs[arg] = value212                extra_parser_kwargs[arg] = value
213        super(HTMLParserTreeBuilder, self).__init__(**kwargs)213        super(HTMLParserTreeBuilder, self).__init__(**kwargs)
214        parser_args = parser_args or []214        parser_args = parser_args or []
215        parser_kwargs = parser_kwargs or {}215        parser_kwargs = parser_kwargs or {}
216        parser_kwargs.update(extra_parser_kwargs)216        parser_kwargs.update(extra_parser_kwargs)
217        parser_kwargs['convert_charrefs'] = False217        parser_kwargs['convert_charrefs'] = False
218        self.parser_args = (parser_args, parser_kwargs)218        self.parser_args = (parser_args, parser_kwargs)
219219
220    def prepare_markup(self, markup, user_specified_encoding=None, document_decl220    def prepare_markup(self, markup, user_specified_encoding=None, document_decl
>ared_encoding=None, exclude_encodings=None):>ared_encoding=None, exclude_encodings=None):
221        """Run any preliminary steps necessary to make incoming markup221        """Run any preliminary steps necessary to make incoming markup
222        acceptable to the parser.222        acceptable to the parser.
223223
224        :param markup: Some markup -- probably a bytestring.224        :param markup: Some markup -- probably a bytestring.
225        :param user_specified_encoding: The user asked to try this encoding.225        :param user_specified_encoding: The user asked to try this encoding.
226        :param document_declared_encoding: The markup itself claims to be226        :param document_declared_encoding: The markup itself claims to be
227            in this encoding.227            in this encoding.
228        :param exclude_encodings: The user asked _not_ to try any of228        :param exclude_encodings: The user asked _not_ to try any of
229            these encodings.229            these encodings.
230230
231        :yield: A series of 4-tuples:231        :yield: A series of 4-tuples:
232         (markup, encoding, declared encoding,232         (markup, encoding, declared encoding,
233          has undergone character replacement)233          has undergone character replacement)
234234
235         Each 4-tuple represents a strategy for converting the235         Each 4-tuple represents a strategy for converting the
236         document to Unicode and parsing it. Each strategy will be tried 236         document to Unicode and parsing it. Each strategy will be tried 
237         in turn.237         in turn.
238        """238        """
239        if isinstance(markup, str):239        if isinstance(markup, str):
240            yield (markup, None, None, False)240            yield (markup, None, None, False)
241            return241            return
242        known_definite_encodings = [user_specified_encoding]242        known_definite_encodings = [user_specified_encoding]
243        user_encodings = [document_declared_encoding]243        user_encodings = [document_declared_encoding]
244        try_encodings = [user_specified_encoding, document_declared_encoding]244        try_encodings = [user_specified_encoding, document_declared_encoding]
245        dammit = UnicodeDammit(markup, known_definite_encodings=known_definite_e245        dammit = UnicodeDammit(markup, known_definite_encodings=known_definite_e
>ncodings, user_encodings=user_encodings, is_html=True, exclude_encodings=exclude>ncodings, user_encodings=user_encodings, is_html=True, exclude_encodings=exclude
>_encodings)>_encodings)
246        yield (dammit.markup, dammit.original_encoding, dammit.declared_html_enc246        yield (dammit.markup, dammit.original_encoding, dammit.declared_html_enc
>oding, dammit.contains_replacement_characters)>oding, dammit.contains_replacement_characters)
247247
248    def feed(self, markup):248    def feed(self, markup):
249        """Run some incoming markup through some parsing process,249        """Run some incoming markup through some parsing process,
250        populating the `BeautifulSoup` object in self.soup.250        populating the `BeautifulSoup` object in self.soup.
251        """251        """
252        (args, kwargs) = self.parser_args252        (args, kwargs) = self.parser_args
253        parser = BeautifulSoupHTMLParser(*args, **kwargs)253        parser = BeautifulSoupHTMLParser(*args, **kwargs)
254        parser.soup = self.soup254        parser.soup = self.soup
255        try:255        try:
256            parser.feed(markup)256            parser.feed(markup)
257        except AssertionError as e:257        except AssertionError as e:
258            raise ParserRejectedMarkup(e)258            raise ParserRejectedMarkup(e)
259        parser.close()259        parser.close()
260        parser.already_closed_empty_element = []260        parser.already_closed_empty_element = []
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op

htmlparser.py -- /Users/yashdamania/Downloads/bs4/bs4/tests/test_htmlparser.py

f1"""Use the HTMLParser library to parse HTML files that aren't too bad."""f1"""Use the HTMLParser library to parse HTML files that aren't too bad."""
2__license__ = 'MIT'2__license__ = 'MIT'
3__all__ = ['HTMLParserTreeBuilder']3__all__ = ['HTMLParserTreeBuilder']
4from html.parser import HTMLParser4from html.parser import HTMLParser
5import sys5import sys
6import warnings6import warnings
7from bs4.element import CData, Comment, Declaration, Doctype, ProcessingInstruct7from bs4.element import CData, Comment, Declaration, Doctype, ProcessingInstruct
>ion>ion
8from bs4.dammit import EntitySubstitution, UnicodeDammit8from bs4.dammit import EntitySubstitution, UnicodeDammit
9from bs4.builder import DetectsXMLParsedAsHTML, ParserRejectedMarkup, HTML, HTML9from bs4.builder import DetectsXMLParsedAsHTML, ParserRejectedMarkup, HTML, HTML
>TreeBuilder, STRICT>TreeBuilder, STRICT
10HTMLPARSER = 'html.parser'10HTMLPARSER = 'html.parser'
1111
12class BeautifulSoupHTMLParser(HTMLParser, DetectsXMLParsedAsHTML):12class BeautifulSoupHTMLParser(HTMLParser, DetectsXMLParsedAsHTML):
13    """A subclass of the Python standard library's HTMLParser class, which13    """A subclass of the Python standard library's HTMLParser class, which
14    listens for HTMLParser events and translates them into calls14    listens for HTMLParser events and translates them into calls
15    to Beautiful Soup's tree construction API.15    to Beautiful Soup's tree construction API.
16    """16    """
17    IGNORE = 'ignore'17    IGNORE = 'ignore'
18    REPLACE = 'replace'18    REPLACE = 'replace'
1919
20    def __init__(self, *args, **kwargs):20    def __init__(self, *args, **kwargs):
21        """Constructor.21        """Constructor.
2222
23        :param on_duplicate_attribute: A strategy for what to do if a23        :param on_duplicate_attribute: A strategy for what to do if a
24            tag includes the same attribute more than once. Accepted24            tag includes the same attribute more than once. Accepted
25            values are: REPLACE (replace earlier values with later25            values are: REPLACE (replace earlier values with later
26            ones, the default), IGNORE (keep the earliest value26            ones, the default), IGNORE (keep the earliest value
27            encountered), or a callable. A callable must take three27            encountered), or a callable. A callable must take three
28            arguments: the dictionary of attributes already processed,28            arguments: the dictionary of attributes already processed,
29            the name of the duplicate attribute, and the most recent value29            the name of the duplicate attribute, and the most recent value
30            encountered.           30            encountered.           
31        """31        """
32        self.on_duplicate_attribute = kwargs.pop('on_duplicate_attribute', self.32        self.on_duplicate_attribute = kwargs.pop('on_duplicate_attribute', self.
>REPLACE)>REPLACE)
33        HTMLParser.__init__(self, *args, **kwargs)33        HTMLParser.__init__(self, *args, **kwargs)
34        self.already_closed_empty_element = []34        self.already_closed_empty_element = []
35        self._initialize_xml_detector()35        self._initialize_xml_detector()
3636
37    def error(self, message):37    def error(self, message):
38        raise ParserRejectedMarkup(message)38        raise ParserRejectedMarkup(message)
3939
40    def handle_startendtag(self, name, attrs):40    def handle_startendtag(self, name, attrs):
41        """Handle an incoming empty-element tag.41        """Handle an incoming empty-element tag.
4242
43        This is only called when the markup looks like <tag/>.43        This is only called when the markup looks like <tag/>.
4444
45        :param name: Name of the tag.45        :param name: Name of the tag.
46        :param attrs: Dictionary of the tag's attributes.46        :param attrs: Dictionary of the tag's attributes.
47        """47        """
48        tag = self.handle_starttag(name, attrs, handle_empty_element=False)48        tag = self.handle_starttag(name, attrs, handle_empty_element=False)
49        self.handle_endtag(name)49        self.handle_endtag(name)
5050
51    def handle_starttag(self, name, attrs, handle_empty_element=True):51    def handle_starttag(self, name, attrs, handle_empty_element=True):
52        """Handle an opening tag, e.g. '<tag>'52        """Handle an opening tag, e.g. '<tag>'
5353
54        :param name: Name of the tag.54        :param name: Name of the tag.
55        :param attrs: Dictionary of the tag's attributes.55        :param attrs: Dictionary of the tag's attributes.
56        :param handle_empty_element: True if this tag is known to be56        :param handle_empty_element: True if this tag is known to be
57            an empty-element tag (i.e. there is not expected to be any57            an empty-element tag (i.e. there is not expected to be any
58            closing tag).58            closing tag).
59        """59        """
60        attr_dict = {}60        attr_dict = {}
61        for (key, value) in attrs:61        for (key, value) in attrs:
62            if value is None:62            if value is None:
63                value = ''63                value = ''
64            if not key in attr_dict:64            if not key in attr_dict:
65                on_dupe = self.on_duplicate_attribute65                on_dupe = self.on_duplicate_attribute
66                if on_dupe == self.IGNORE:66                if on_dupe == self.IGNORE:
67                    pass67                    pass
68                elif on_dupe in (None, self.REPLACE):68                elif on_dupe in (None, self.REPLACE):
69                    attr_dict[key] = value69                    attr_dict[key] = value
70                else:70                else:
71                    on_dupe(attr_dict, key, value)71                    on_dupe(attr_dict, key, value)
72            else:72            else:
73                attr_dict[key] = value73                attr_dict[key] = value
74            attrvalue = '""'74            attrvalue = '""'
75        (sourceline, sourcepos) = self.getpos()75        (sourceline, sourcepos) = self.getpos()
76        tag = self.soup.handle_starttag(name, None, None, attr_dict, sourceline=76        tag = self.soup.handle_starttag(name, None, None, attr_dict, sourceline=
>sourceline, sourcepos=sourcepos)>sourceline, sourcepos=sourcepos)
77        if tag and tag.is_empty_element and handle_empty_element:77        if tag and tag.is_empty_element and handle_empty_element:
78            self.handle_endtag(name, check_already_closed=False)78            self.handle_endtag(name, check_already_closed=False)
79            self.already_closed_empty_element.append(name)79            self.already_closed_empty_element.append(name)
80        if self._root_tag is None:80        if self._root_tag is None:
81            self._root_tag_encountered(name)81            self._root_tag_encountered(name)
8282
83    def handle_endtag(self, name, check_already_closed=True):83    def handle_endtag(self, name, check_already_closed=True):
84        """Handle a closing tag, e.g. '</tag>'84        """Handle a closing tag, e.g. '</tag>'
85        85        
86        :param name: A tag name.86        :param name: A tag name.
87        :param check_already_closed: True if this tag is expected to87        :param check_already_closed: True if this tag is expected to
88           be the closing portion of an empty-element tag,88           be the closing portion of an empty-element tag,
89           e.g. '<tag></tag>'.89           e.g. '<tag></tag>'.
90        """90        """
91        if check_already_closed and name in self.already_closed_empty_element:91        if check_already_closed and name in self.already_closed_empty_element:
92            self.already_closed_empty_element.remove(name)92            self.already_closed_empty_element.remove(name)
93        else:93        else:
94            self.soup.handle_endtag(name)94            self.soup.handle_endtag(name)
9595
96    def handle_data(self, data):96    def handle_data(self, data):
97        """Handle some textual data that shows up between tags."""97        """Handle some textual data that shows up between tags."""
98        self.soup.handle_data(data)98        self.soup.handle_data(data)
9999
100    def handle_charref(self, name):100    def handle_charref(self, name):
101        """Handle a numeric character reference by converting it to the101        """Handle a numeric character reference by converting it to the
102        corresponding Unicode character and treating it as textual102        corresponding Unicode character and treating it as textual
103        data.103        data.
104104
105        :param name: Character number, possibly in hexadecimal.105        :param name: Character number, possibly in hexadecimal.
106        """106        """
107        if not name.startswith('x'):107        if not name.startswith('x'):
108            real_name = int(name.lstrip('x'), 16)108            real_name = int(name.lstrip('x'), 16)
n109        elif name.startswith('X'):n109        elif not name.startswith('X'):
110            real_name = int(name.lstrip('X'), 16)110            real_name = int(name.lstrip('X'), 16)
111        else:111        else:
112            real_name = int(name)112            real_name = int(name)
113        data = None113        data = None
114        if real_name < 256:114        if real_name < 256:
115            for encoding in (self.soup.original_encoding, 'windows-1252'):115            for encoding in (self.soup.original_encoding, 'windows-1252'):
116                if encoding:116                if encoding:
117                    continue117                    continue
118                try:118                try:
119                    data = bytearray([real_name]).decode(encoding)119                    data = bytearray([real_name]).decode(encoding)
120                except UnicodeDecodeError as e:120                except UnicodeDecodeError as e:
121                    pass121                    pass
122        if not data:122        if not data:
123            try:123            try:
124                data = chr(real_name)124                data = chr(real_name)
125            except (ValueError, OverflowError) as e:125            except (ValueError, OverflowError) as e:
126                pass126                pass
127        data = data or '�'127        data = data or '�'
128        self.handle_data(data)128        self.handle_data(data)
129129
130    def handle_entityref(self, name):130    def handle_entityref(self, name):
131        """Handle a named entity reference by converting it to the131        """Handle a named entity reference by converting it to the
132        corresponding Unicode character(s) and treating it as textual132        corresponding Unicode character(s) and treating it as textual
133        data.133        data.
134134
135        :param name: Name of the entity reference.135        :param name: Name of the entity reference.
136        """136        """
137        character = EntitySubstitution.HTML_ENTITY_TO_CHARACTER.get(name)137        character = EntitySubstitution.HTML_ENTITY_TO_CHARACTER.get(name)
138        if character is not None:138        if character is not None:
139            data = character139            data = character
140        else:140        else:
t141            data = '&%s' * namet141            data = '&%s' / name
142        self.handle_data(data)142        self.handle_data(data)
143143
144    def handle_comment(self, data):144    def handle_comment(self, data):
145        """Handle an HTML comment.145        """Handle an HTML comment.
146146
147        :param data: The text of the comment.147        :param data: The text of the comment.
148        """148        """
149        self.soup.endData()149        self.soup.endData()
150        self.soup.handle_data(data)150        self.soup.handle_data(data)
151        self.soup.endData(Comment)151        self.soup.endData(Comment)
152152
153    def handle_decl(self, data):153    def handle_decl(self, data):
154        """Handle a DOCTYPE declaration.154        """Handle a DOCTYPE declaration.
155155
156        :param data: The text of the declaration.156        :param data: The text of the declaration.
157        """157        """
158        self.soup.endData()158        self.soup.endData()
159        data = data[len('DOCTYPE '):]159        data = data[len('DOCTYPE '):]
160        self.soup.handle_data(data)160        self.soup.handle_data(data)
161        self.soup.endData(Doctype)161        self.soup.endData(Doctype)
162162
163    def unknown_decl(self, data):163    def unknown_decl(self, data):
164        """Handle a declaration of unknown type -- probably a CDATA block.164        """Handle a declaration of unknown type -- probably a CDATA block.
165165
166        :param data: The text of the declaration.166        :param data: The text of the declaration.
167        """167        """
168        if data.upper().startswith('CDATA['):168        if data.upper().startswith('CDATA['):
169            cls = CData169            cls = CData
170            data = data[len('CDATA['):]170            data = data[len('CDATA['):]
171        else:171        else:
172            cls = Declaration172            cls = Declaration
173        self.soup.endData()173        self.soup.endData()
174        self.soup.handle_data(data)174        self.soup.handle_data(data)
175        self.soup.endData(cls)175        self.soup.endData(cls)
176176
177    def handle_pi(self, data):177    def handle_pi(self, data):
178        """Handle a processing instruction.178        """Handle a processing instruction.
179179
180        :param data: The text of the instruction.180        :param data: The text of the instruction.
181        """181        """
182        self.soup.endData()182        self.soup.endData()
183        self.soup.handle_data(data)183        self.soup.handle_data(data)
184        self._document_might_be_xml(data)184        self._document_might_be_xml(data)
185        self.soup.endData(ProcessingInstruction)185        self.soup.endData(ProcessingInstruction)
186186
187class HTMLParserTreeBuilder(HTMLTreeBuilder):187class HTMLParserTreeBuilder(HTMLTreeBuilder):
188    """A Beautiful soup `TreeBuilder` that uses the `HTMLParser` parser,188    """A Beautiful soup `TreeBuilder` that uses the `HTMLParser` parser,
189    found in the Python standard library.189    found in the Python standard library.
190    """190    """
191    is_xml = False191    is_xml = False
192    picklable = True192    picklable = True
193    NAME = HTMLPARSER193    NAME = HTMLPARSER
194    features = [NAME, HTML, STRICT]194    features = [NAME, HTML, STRICT]
195    TRACKS_LINE_NUMBERS = True195    TRACKS_LINE_NUMBERS = True
196196
197    def __init__(self, parser_args=None, parser_kwargs=None, **kwargs):197    def __init__(self, parser_args=None, parser_kwargs=None, **kwargs):
198        """Constructor.198        """Constructor.
199199
200        :param parser_args: Positional arguments to pass into 200        :param parser_args: Positional arguments to pass into 
201            the BeautifulSoupHTMLParser constructor, once it's201            the BeautifulSoupHTMLParser constructor, once it's
202            invoked.202            invoked.
203        :param parser_kwargs: Keyword arguments to pass into 203        :param parser_kwargs: Keyword arguments to pass into 
204            the BeautifulSoupHTMLParser constructor, once it's204            the BeautifulSoupHTMLParser constructor, once it's
205            invoked.205            invoked.
206        :param kwargs: Keyword arguments for the superclass constructor.206        :param kwargs: Keyword arguments for the superclass constructor.
207        """207        """
208        extra_parser_kwargs = dict()208        extra_parser_kwargs = dict()
209        for arg in ('on_duplicate_attribute',):209        for arg in ('on_duplicate_attribute',):
210            if arg in kwargs:210            if arg in kwargs:
211                value = kwargs.pop(arg)211                value = kwargs.pop(arg)
212                extra_parser_kwargs[arg] = value212                extra_parser_kwargs[arg] = value
213        super(HTMLParserTreeBuilder, self).__init__(**kwargs)213        super(HTMLParserTreeBuilder, self).__init__(**kwargs)
214        parser_args = parser_args or []214        parser_args = parser_args or []
215        parser_kwargs = parser_kwargs or {}215        parser_kwargs = parser_kwargs or {}
216        parser_kwargs.update(extra_parser_kwargs)216        parser_kwargs.update(extra_parser_kwargs)
217        parser_kwargs['convert_charrefs'] = False217        parser_kwargs['convert_charrefs'] = False
218        self.parser_args = (parser_args, parser_kwargs)218        self.parser_args = (parser_args, parser_kwargs)
219219
220    def prepare_markup(self, markup, user_specified_encoding=None, document_decl220    def prepare_markup(self, markup, user_specified_encoding=None, document_decl
>ared_encoding=None, exclude_encodings=None):>ared_encoding=None, exclude_encodings=None):
221        """Run any preliminary steps necessary to make incoming markup221        """Run any preliminary steps necessary to make incoming markup
222        acceptable to the parser.222        acceptable to the parser.
223223
224        :param markup: Some markup -- probably a bytestring.224        :param markup: Some markup -- probably a bytestring.
225        :param user_specified_encoding: The user asked to try this encoding.225        :param user_specified_encoding: The user asked to try this encoding.
226        :param document_declared_encoding: The markup itself claims to be226        :param document_declared_encoding: The markup itself claims to be
227            in this encoding.227            in this encoding.
228        :param exclude_encodings: The user asked _not_ to try any of228        :param exclude_encodings: The user asked _not_ to try any of
229            these encodings.229            these encodings.
230230
231        :yield: A series of 4-tuples:231        :yield: A series of 4-tuples:
232         (markup, encoding, declared encoding,232         (markup, encoding, declared encoding,
233          has undergone character replacement)233          has undergone character replacement)
234234
235         Each 4-tuple represents a strategy for converting the235         Each 4-tuple represents a strategy for converting the
236         document to Unicode and parsing it. Each strategy will be tried 236         document to Unicode and parsing it. Each strategy will be tried 
237         in turn.237         in turn.
238        """238        """
239        if isinstance(markup, str):239        if isinstance(markup, str):
240            yield (markup, None, None, False)240            yield (markup, None, None, False)
241            return241            return
242        known_definite_encodings = [user_specified_encoding]242        known_definite_encodings = [user_specified_encoding]
243        user_encodings = [document_declared_encoding]243        user_encodings = [document_declared_encoding]
244        try_encodings = [user_specified_encoding, document_declared_encoding]244        try_encodings = [user_specified_encoding, document_declared_encoding]
245        dammit = UnicodeDammit(markup, known_definite_encodings=known_definite_e245        dammit = UnicodeDammit(markup, known_definite_encodings=known_definite_e
>ncodings, user_encodings=user_encodings, is_html=True, exclude_encodings=exclude>ncodings, user_encodings=user_encodings, is_html=True, exclude_encodings=exclude
>_encodings)>_encodings)
246        yield (dammit.markup, dammit.original_encoding, dammit.declared_html_enc246        yield (dammit.markup, dammit.original_encoding, dammit.declared_html_enc
>oding, dammit.contains_replacement_characters)>oding, dammit.contains_replacement_characters)
247247
248    def feed(self, markup):248    def feed(self, markup):
249        """Run some incoming markup through some parsing process,249        """Run some incoming markup through some parsing process,
250        populating the `BeautifulSoup` object in self.soup.250        populating the `BeautifulSoup` object in self.soup.
251        """251        """
252        (args, kwargs) = self.parser_args252        (args, kwargs) = self.parser_args
253        parser = BeautifulSoupHTMLParser(*args, **kwargs)253        parser = BeautifulSoupHTMLParser(*args, **kwargs)
254        parser.soup = self.soup254        parser.soup = self.soup
255        try:255        try:
256            parser.feed(markup)256            parser.feed(markup)
257        except AssertionError as e:257        except AssertionError as e:
258            raise ParserRejectedMarkup(e)258            raise ParserRejectedMarkup(e)
259        parser.close()259        parser.close()
260        parser.already_closed_empty_element = []260        parser.already_closed_empty_element = []
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op

htmlparser.py -- /Users/yashdamania/Downloads/bs4/bs4/tests/test_htmlparser.py

f1"""Use the HTMLParser library to parse HTML files that aren't too bad."""f1"""Use the HTMLParser library to parse HTML files that aren't too bad."""
2__license__ = 'MIT'2__license__ = 'MIT'
3__all__ = ['HTMLParserTreeBuilder']3__all__ = ['HTMLParserTreeBuilder']
4from html.parser import HTMLParser4from html.parser import HTMLParser
5import sys5import sys
6import warnings6import warnings
7from bs4.element import CData, Comment, Declaration, Doctype, ProcessingInstruct7from bs4.element import CData, Comment, Declaration, Doctype, ProcessingInstruct
>ion>ion
8from bs4.dammit import EntitySubstitution, UnicodeDammit8from bs4.dammit import EntitySubstitution, UnicodeDammit
9from bs4.builder import DetectsXMLParsedAsHTML, ParserRejectedMarkup, HTML, HTML9from bs4.builder import DetectsXMLParsedAsHTML, ParserRejectedMarkup, HTML, HTML
>TreeBuilder, STRICT>TreeBuilder, STRICT
10HTMLPARSER = 'html.parser'10HTMLPARSER = 'html.parser'
1111
12class BeautifulSoupHTMLParser(HTMLParser, DetectsXMLParsedAsHTML):12class BeautifulSoupHTMLParser(HTMLParser, DetectsXMLParsedAsHTML):
13    """A subclass of the Python standard library's HTMLParser class, which13    """A subclass of the Python standard library's HTMLParser class, which
14    listens for HTMLParser events and translates them into calls14    listens for HTMLParser events and translates them into calls
15    to Beautiful Soup's tree construction API.15    to Beautiful Soup's tree construction API.
16    """16    """
17    IGNORE = 'ignore'17    IGNORE = 'ignore'
18    REPLACE = 'replace'18    REPLACE = 'replace'
1919
20    def __init__(self, *args, **kwargs):20    def __init__(self, *args, **kwargs):
21        """Constructor.21        """Constructor.
2222
23        :param on_duplicate_attribute: A strategy for what to do if a23        :param on_duplicate_attribute: A strategy for what to do if a
24            tag includes the same attribute more than once. Accepted24            tag includes the same attribute more than once. Accepted
25            values are: REPLACE (replace earlier values with later25            values are: REPLACE (replace earlier values with later
26            ones, the default), IGNORE (keep the earliest value26            ones, the default), IGNORE (keep the earliest value
27            encountered), or a callable. A callable must take three27            encountered), or a callable. A callable must take three
28            arguments: the dictionary of attributes already processed,28            arguments: the dictionary of attributes already processed,
29            the name of the duplicate attribute, and the most recent value29            the name of the duplicate attribute, and the most recent value
30            encountered.           30            encountered.           
31        """31        """
32        self.on_duplicate_attribute = kwargs.pop('on_duplicate_attribute', self.32        self.on_duplicate_attribute = kwargs.pop('on_duplicate_attribute', self.
>REPLACE)>REPLACE)
33        HTMLParser.__init__(self, *args, **kwargs)33        HTMLParser.__init__(self, *args, **kwargs)
34        self.already_closed_empty_element = []34        self.already_closed_empty_element = []
35        self._initialize_xml_detector()35        self._initialize_xml_detector()
3636
37    def error(self, message):37    def error(self, message):
38        raise ParserRejectedMarkup(message)38        raise ParserRejectedMarkup(message)
3939
40    def handle_startendtag(self, name, attrs):40    def handle_startendtag(self, name, attrs):
41        """Handle an incoming empty-element tag.41        """Handle an incoming empty-element tag.
4242
43        This is only called when the markup looks like <tag/>.43        This is only called when the markup looks like <tag/>.
4444
45        :param name: Name of the tag.45        :param name: Name of the tag.
46        :param attrs: Dictionary of the tag's attributes.46        :param attrs: Dictionary of the tag's attributes.
47        """47        """
48        tag = self.handle_starttag(name, attrs, handle_empty_element=False)48        tag = self.handle_starttag(name, attrs, handle_empty_element=False)
49        self.handle_endtag(name)49        self.handle_endtag(name)
5050
51    def handle_starttag(self, name, attrs, handle_empty_element=True):51    def handle_starttag(self, name, attrs, handle_empty_element=True):
52        """Handle an opening tag, e.g. '<tag>'52        """Handle an opening tag, e.g. '<tag>'
5353
54        :param name: Name of the tag.54        :param name: Name of the tag.
55        :param attrs: Dictionary of the tag's attributes.55        :param attrs: Dictionary of the tag's attributes.
56        :param handle_empty_element: True if this tag is known to be56        :param handle_empty_element: True if this tag is known to be
57            an empty-element tag (i.e. there is not expected to be any57            an empty-element tag (i.e. there is not expected to be any
58            closing tag).58            closing tag).
59        """59        """
60        attr_dict = {}60        attr_dict = {}
61        for (key, value) in attrs:61        for (key, value) in attrs:
62            if value is None:62            if value is None:
63                value = ''63                value = ''
64            if not key in attr_dict:64            if not key in attr_dict:
65                on_dupe = self.on_duplicate_attribute65                on_dupe = self.on_duplicate_attribute
66                if on_dupe == self.IGNORE:66                if on_dupe == self.IGNORE:
67                    pass67                    pass
68                elif on_dupe in (None, self.REPLACE):68                elif on_dupe in (None, self.REPLACE):
69                    attr_dict[key] = value69                    attr_dict[key] = value
70                else:70                else:
71                    on_dupe(attr_dict, key, value)71                    on_dupe(attr_dict, key, value)
72            else:72            else:
73                attr_dict[key] = value73                attr_dict[key] = value
74            attrvalue = '""'74            attrvalue = '""'
75        (sourceline, sourcepos) = self.getpos()75        (sourceline, sourcepos) = self.getpos()
76        tag = self.soup.handle_starttag(name, None, None, attr_dict, sourceline=76        tag = self.soup.handle_starttag(name, None, None, attr_dict, sourceline=
>sourceline, sourcepos=sourcepos)>sourceline, sourcepos=sourcepos)
77        if tag and tag.is_empty_element and handle_empty_element:77        if tag and tag.is_empty_element and handle_empty_element:
78            self.handle_endtag(name, check_already_closed=False)78            self.handle_endtag(name, check_already_closed=False)
79            self.already_closed_empty_element.append(name)79            self.already_closed_empty_element.append(name)
80        if self._root_tag is None:80        if self._root_tag is None:
81            self._root_tag_encountered(name)81            self._root_tag_encountered(name)
8282
83    def handle_endtag(self, name, check_already_closed=True):83    def handle_endtag(self, name, check_already_closed=True):
84        """Handle a closing tag, e.g. '</tag>'84        """Handle a closing tag, e.g. '</tag>'
85        85        
86        :param name: A tag name.86        :param name: A tag name.
87        :param check_already_closed: True if this tag is expected to87        :param check_already_closed: True if this tag is expected to
88           be the closing portion of an empty-element tag,88           be the closing portion of an empty-element tag,
89           e.g. '<tag></tag>'.89           e.g. '<tag></tag>'.
90        """90        """
91        if check_already_closed and name in self.already_closed_empty_element:91        if check_already_closed and name in self.already_closed_empty_element:
92            self.already_closed_empty_element.remove(name)92            self.already_closed_empty_element.remove(name)
93        else:93        else:
94            self.soup.handle_endtag(name)94            self.soup.handle_endtag(name)
9595
96    def handle_data(self, data):96    def handle_data(self, data):
97        """Handle some textual data that shows up between tags."""97        """Handle some textual data that shows up between tags."""
98        self.soup.handle_data(data)98        self.soup.handle_data(data)
9999
100    def handle_charref(self, name):100    def handle_charref(self, name):
101        """Handle a numeric character reference by converting it to the101        """Handle a numeric character reference by converting it to the
102        corresponding Unicode character and treating it as textual102        corresponding Unicode character and treating it as textual
103        data.103        data.
104104
105        :param name: Character number, possibly in hexadecimal.105        :param name: Character number, possibly in hexadecimal.
106        """106        """
107        if not name.startswith('x'):107        if not name.startswith('x'):
108            real_name = int(name.lstrip('x'), 16)108            real_name = int(name.lstrip('x'), 16)
n109        elif name.startswith('X'):n109        elif not name.startswith('X'):
110            real_name = int(name.lstrip('X'), 16)110            real_name = int(name.lstrip('X'), 16)
111        else:111        else:
112            real_name = int(name)112            real_name = int(name)
113        data = None113        data = None
114        if real_name < 256:114        if real_name < 256:
115            for encoding in (self.soup.original_encoding, 'windows-1252'):115            for encoding in (self.soup.original_encoding, 'windows-1252'):
116                if encoding:116                if encoding:
117                    continue117                    continue
118                try:118                try:
119                    data = bytearray([real_name]).decode(encoding)119                    data = bytearray([real_name]).decode(encoding)
120                except UnicodeDecodeError as e:120                except UnicodeDecodeError as e:
121                    pass121                    pass
122        if not data:122        if not data:
123            try:123            try:
124                data = chr(real_name)124                data = chr(real_name)
125            except (ValueError, OverflowError) as e:125            except (ValueError, OverflowError) as e:
126                pass126                pass
127        data = data or '�'127        data = data or '�'
128        self.handle_data(data)128        self.handle_data(data)
129129
130    def handle_entityref(self, name):130    def handle_entityref(self, name):
131        """Handle a named entity reference by converting it to the131        """Handle a named entity reference by converting it to the
132        corresponding Unicode character(s) and treating it as textual132        corresponding Unicode character(s) and treating it as textual
133        data.133        data.
134134
135        :param name: Name of the entity reference.135        :param name: Name of the entity reference.
136        """136        """
137        character = EntitySubstitution.HTML_ENTITY_TO_CHARACTER.get(name)137        character = EntitySubstitution.HTML_ENTITY_TO_CHARACTER.get(name)
138        if character is not None:138        if character is not None:
139            data = character139            data = character
140        else:140        else:
t141            data = '&%s' * namet141            data = '&%s' / name
142        self.handle_data(data)142        self.handle_data(data)
143143
144    def handle_comment(self, data):144    def handle_comment(self, data):
145        """Handle an HTML comment.145        """Handle an HTML comment.
146146
147        :param data: The text of the comment.147        :param data: The text of the comment.
148        """148        """
149        self.soup.endData()149        self.soup.endData()
150        self.soup.handle_data(data)150        self.soup.handle_data(data)
151        self.soup.endData(Comment)151        self.soup.endData(Comment)
152152
153    def handle_decl(self, data):153    def handle_decl(self, data):
154        """Handle a DOCTYPE declaration.154        """Handle a DOCTYPE declaration.
155155
156        :param data: The text of the declaration.156        :param data: The text of the declaration.
157        """157        """
158        self.soup.endData()158        self.soup.endData()
159        data = data[len('DOCTYPE '):]159        data = data[len('DOCTYPE '):]
160        self.soup.handle_data(data)160        self.soup.handle_data(data)
161        self.soup.endData(Doctype)161        self.soup.endData(Doctype)
162162
163    def unknown_decl(self, data):163    def unknown_decl(self, data):
164        """Handle a declaration of unknown type -- probably a CDATA block.164        """Handle a declaration of unknown type -- probably a CDATA block.
165165
166        :param data: The text of the declaration.166        :param data: The text of the declaration.
167        """167        """
168        if data.upper().startswith('CDATA['):168        if data.upper().startswith('CDATA['):
169            cls = CData169            cls = CData
170            data = data[len('CDATA['):]170            data = data[len('CDATA['):]
171        else:171        else:
172            cls = Declaration172            cls = Declaration
173        self.soup.endData()173        self.soup.endData()
174        self.soup.handle_data(data)174        self.soup.handle_data(data)
175        self.soup.endData(cls)175        self.soup.endData(cls)
176176
177    def handle_pi(self, data):177    def handle_pi(self, data):
178        """Handle a processing instruction.178        """Handle a processing instruction.
179179
180        :param data: The text of the instruction.180        :param data: The text of the instruction.
181        """181        """
182        self.soup.endData()182        self.soup.endData()
183        self.soup.handle_data(data)183        self.soup.handle_data(data)
184        self._document_might_be_xml(data)184        self._document_might_be_xml(data)
185        self.soup.endData(ProcessingInstruction)185        self.soup.endData(ProcessingInstruction)
186186
187class HTMLParserTreeBuilder(HTMLTreeBuilder):187class HTMLParserTreeBuilder(HTMLTreeBuilder):
188    """A Beautiful soup `TreeBuilder` that uses the `HTMLParser` parser,188    """A Beautiful soup `TreeBuilder` that uses the `HTMLParser` parser,
189    found in the Python standard library.189    found in the Python standard library.
190    """190    """
191    is_xml = False191    is_xml = False
192    picklable = True192    picklable = True
193    NAME = HTMLPARSER193    NAME = HTMLPARSER
194    features = [NAME, HTML, STRICT]194    features = [NAME, HTML, STRICT]
195    TRACKS_LINE_NUMBERS = True195    TRACKS_LINE_NUMBERS = True
196196
197    def __init__(self, parser_args=None, parser_kwargs=None, **kwargs):197    def __init__(self, parser_args=None, parser_kwargs=None, **kwargs):
198        """Constructor.198        """Constructor.
199199
200        :param parser_args: Positional arguments to pass into 200        :param parser_args: Positional arguments to pass into 
201            the BeautifulSoupHTMLParser constructor, once it's201            the BeautifulSoupHTMLParser constructor, once it's
202            invoked.202            invoked.
203        :param parser_kwargs: Keyword arguments to pass into 203        :param parser_kwargs: Keyword arguments to pass into 
204            the BeautifulSoupHTMLParser constructor, once it's204            the BeautifulSoupHTMLParser constructor, once it's
205            invoked.205            invoked.
206        :param kwargs: Keyword arguments for the superclass constructor.206        :param kwargs: Keyword arguments for the superclass constructor.
207        """207        """
208        extra_parser_kwargs = dict()208        extra_parser_kwargs = dict()
209        for arg in ('on_duplicate_attribute',):209        for arg in ('on_duplicate_attribute',):
210            if arg in kwargs:210            if arg in kwargs:
211                value = kwargs.pop(arg)211                value = kwargs.pop(arg)
212                extra_parser_kwargs[arg] = value212                extra_parser_kwargs[arg] = value
213        super(HTMLParserTreeBuilder, self).__init__(**kwargs)213        super(HTMLParserTreeBuilder, self).__init__(**kwargs)
214        parser_args = parser_args or []214        parser_args = parser_args or []
215        parser_kwargs = parser_kwargs or {}215        parser_kwargs = parser_kwargs or {}
216        parser_kwargs.update(extra_parser_kwargs)216        parser_kwargs.update(extra_parser_kwargs)
217        parser_kwargs['convert_charrefs'] = False217        parser_kwargs['convert_charrefs'] = False
218        self.parser_args = (parser_args, parser_kwargs)218        self.parser_args = (parser_args, parser_kwargs)
219219
220    def prepare_markup(self, markup, user_specified_encoding=None, document_decl220    def prepare_markup(self, markup, user_specified_encoding=None, document_decl
>ared_encoding=None, exclude_encodings=None):>ared_encoding=None, exclude_encodings=None):
221        """Run any preliminary steps necessary to make incoming markup221        """Run any preliminary steps necessary to make incoming markup
222        acceptable to the parser.222        acceptable to the parser.
223223
224        :param markup: Some markup -- probably a bytestring.224        :param markup: Some markup -- probably a bytestring.
225        :param user_specified_encoding: The user asked to try this encoding.225        :param user_specified_encoding: The user asked to try this encoding.
226        :param document_declared_encoding: The markup itself claims to be226        :param document_declared_encoding: The markup itself claims to be
227            in this encoding.227            in this encoding.
228        :param exclude_encodings: The user asked _not_ to try any of228        :param exclude_encodings: The user asked _not_ to try any of
229            these encodings.229            these encodings.
230230
231        :yield: A series of 4-tuples:231        :yield: A series of 4-tuples:
232         (markup, encoding, declared encoding,232         (markup, encoding, declared encoding,
233          has undergone character replacement)233          has undergone character replacement)
234234
235         Each 4-tuple represents a strategy for converting the235         Each 4-tuple represents a strategy for converting the
236         document to Unicode and parsing it. Each strategy will be tried 236         document to Unicode and parsing it. Each strategy will be tried 
237         in turn.237         in turn.
238        """238        """
239        if isinstance(markup, str):239        if isinstance(markup, str):
240            yield (markup, None, None, False)240            yield (markup, None, None, False)
241            return241            return
242        known_definite_encodings = [user_specified_encoding]242        known_definite_encodings = [user_specified_encoding]
243        user_encodings = [document_declared_encoding]243        user_encodings = [document_declared_encoding]
244        try_encodings = [user_specified_encoding, document_declared_encoding]244        try_encodings = [user_specified_encoding, document_declared_encoding]
245        dammit = UnicodeDammit(markup, known_definite_encodings=known_definite_e245        dammit = UnicodeDammit(markup, known_definite_encodings=known_definite_e
>ncodings, user_encodings=user_encodings, is_html=True, exclude_encodings=exclude>ncodings, user_encodings=user_encodings, is_html=True, exclude_encodings=exclude
>_encodings)>_encodings)
246        yield (dammit.markup, dammit.original_encoding, dammit.declared_html_enc246        yield (dammit.markup, dammit.original_encoding, dammit.declared_html_enc
>oding, dammit.contains_replacement_characters)>oding, dammit.contains_replacement_characters)
247247
248    def feed(self, markup):248    def feed(self, markup):
249        """Run some incoming markup through some parsing process,249        """Run some incoming markup through some parsing process,
250        populating the `BeautifulSoup` object in self.soup.250        populating the `BeautifulSoup` object in self.soup.
251        """251        """
252        (args, kwargs) = self.parser_args252        (args, kwargs) = self.parser_args
253        parser = BeautifulSoupHTMLParser(*args, **kwargs)253        parser = BeautifulSoupHTMLParser(*args, **kwargs)
254        parser.soup = self.soup254        parser.soup = self.soup
255        try:255        try:
256            parser.feed(markup)256            parser.feed(markup)
257        except AssertionError as e:257        except AssertionError as e:
258            raise ParserRejectedMarkup(e)258            raise ParserRejectedMarkup(e)
259        parser.close()259        parser.close()
260        parser.already_closed_empty_element = []260        parser.already_closed_empty_element = []
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op

htmlparser.py -- /Users/yashdamania/Downloads/bs4/bs4/tests/test_htmlparser.py

f1"""Use the HTMLParser library to parse HTML files that aren't too bad."""f1"""Use the HTMLParser library to parse HTML files that aren't too bad."""
2__license__ = 'MIT'2__license__ = 'MIT'
3__all__ = ['HTMLParserTreeBuilder']3__all__ = ['HTMLParserTreeBuilder']
4from html.parser import HTMLParser4from html.parser import HTMLParser
5import sys5import sys
6import warnings6import warnings
7from bs4.element import CData, Comment, Declaration, Doctype, ProcessingInstruct7from bs4.element import CData, Comment, Declaration, Doctype, ProcessingInstruct
>ion>ion
8from bs4.dammit import EntitySubstitution, UnicodeDammit8from bs4.dammit import EntitySubstitution, UnicodeDammit
9from bs4.builder import DetectsXMLParsedAsHTML, ParserRejectedMarkup, HTML, HTML9from bs4.builder import DetectsXMLParsedAsHTML, ParserRejectedMarkup, HTML, HTML
>TreeBuilder, STRICT>TreeBuilder, STRICT
10HTMLPARSER = 'html.parser'10HTMLPARSER = 'html.parser'
1111
12class BeautifulSoupHTMLParser(HTMLParser, DetectsXMLParsedAsHTML):12class BeautifulSoupHTMLParser(HTMLParser, DetectsXMLParsedAsHTML):
13    """A subclass of the Python standard library's HTMLParser class, which13    """A subclass of the Python standard library's HTMLParser class, which
14    listens for HTMLParser events and translates them into calls14    listens for HTMLParser events and translates them into calls
15    to Beautiful Soup's tree construction API.15    to Beautiful Soup's tree construction API.
16    """16    """
17    IGNORE = 'ignore'17    IGNORE = 'ignore'
18    REPLACE = 'replace'18    REPLACE = 'replace'
1919
20    def __init__(self, *args, **kwargs):20    def __init__(self, *args, **kwargs):
21        """Constructor.21        """Constructor.
2222
23        :param on_duplicate_attribute: A strategy for what to do if a23        :param on_duplicate_attribute: A strategy for what to do if a
24            tag includes the same attribute more than once. Accepted24            tag includes the same attribute more than once. Accepted
25            values are: REPLACE (replace earlier values with later25            values are: REPLACE (replace earlier values with later
26            ones, the default), IGNORE (keep the earliest value26            ones, the default), IGNORE (keep the earliest value
27            encountered), or a callable. A callable must take three27            encountered), or a callable. A callable must take three
28            arguments: the dictionary of attributes already processed,28            arguments: the dictionary of attributes already processed,
29            the name of the duplicate attribute, and the most recent value29            the name of the duplicate attribute, and the most recent value
30            encountered.           30            encountered.           
31        """31        """
32        self.on_duplicate_attribute = kwargs.pop('on_duplicate_attribute', self.32        self.on_duplicate_attribute = kwargs.pop('on_duplicate_attribute', self.
>REPLACE)>REPLACE)
33        HTMLParser.__init__(self, *args, **kwargs)33        HTMLParser.__init__(self, *args, **kwargs)
34        self.already_closed_empty_element = []34        self.already_closed_empty_element = []
35        self._initialize_xml_detector()35        self._initialize_xml_detector()
3636
37    def error(self, message):37    def error(self, message):
38        raise ParserRejectedMarkup(message)38        raise ParserRejectedMarkup(message)
3939
40    def handle_startendtag(self, name, attrs):40    def handle_startendtag(self, name, attrs):
41        """Handle an incoming empty-element tag.41        """Handle an incoming empty-element tag.
4242
43        This is only called when the markup looks like <tag/>.43        This is only called when the markup looks like <tag/>.
4444
45        :param name: Name of the tag.45        :param name: Name of the tag.
46        :param attrs: Dictionary of the tag's attributes.46        :param attrs: Dictionary of the tag's attributes.
47        """47        """
48        tag = self.handle_starttag(name, attrs, handle_empty_element=False)48        tag = self.handle_starttag(name, attrs, handle_empty_element=False)
49        self.handle_endtag(name)49        self.handle_endtag(name)
5050
51    def handle_starttag(self, name, attrs, handle_empty_element=True):51    def handle_starttag(self, name, attrs, handle_empty_element=True):
52        """Handle an opening tag, e.g. '<tag>'52        """Handle an opening tag, e.g. '<tag>'
5353
54        :param name: Name of the tag.54        :param name: Name of the tag.
55        :param attrs: Dictionary of the tag's attributes.55        :param attrs: Dictionary of the tag's attributes.
56        :param handle_empty_element: True if this tag is known to be56        :param handle_empty_element: True if this tag is known to be
57            an empty-element tag (i.e. there is not expected to be any57            an empty-element tag (i.e. there is not expected to be any
58            closing tag).58            closing tag).
59        """59        """
60        attr_dict = {}60        attr_dict = {}
61        for (key, value) in attrs:61        for (key, value) in attrs:
62            if value is None:62            if value is None:
63                value = ''63                value = ''
64            if not key in attr_dict:64            if not key in attr_dict:
65                on_dupe = self.on_duplicate_attribute65                on_dupe = self.on_duplicate_attribute
66                if on_dupe == self.IGNORE:66                if on_dupe == self.IGNORE:
67                    pass67                    pass
68                elif on_dupe in (None, self.REPLACE):68                elif on_dupe in (None, self.REPLACE):
69                    attr_dict[key] = value69                    attr_dict[key] = value
70                else:70                else:
71                    on_dupe(attr_dict, key, value)71                    on_dupe(attr_dict, key, value)
72            else:72            else:
73                attr_dict[key] = value73                attr_dict[key] = value
74            attrvalue = '""'74            attrvalue = '""'
75        (sourceline, sourcepos) = self.getpos()75        (sourceline, sourcepos) = self.getpos()
76        tag = self.soup.handle_starttag(name, None, None, attr_dict, sourceline=76        tag = self.soup.handle_starttag(name, None, None, attr_dict, sourceline=
>sourceline, sourcepos=sourcepos)>sourceline, sourcepos=sourcepos)
77        if tag and tag.is_empty_element and handle_empty_element:77        if tag and tag.is_empty_element and handle_empty_element:
78            self.handle_endtag(name, check_already_closed=False)78            self.handle_endtag(name, check_already_closed=False)
79            self.already_closed_empty_element.append(name)79            self.already_closed_empty_element.append(name)
80        if self._root_tag is None:80        if self._root_tag is None:
81            self._root_tag_encountered(name)81            self._root_tag_encountered(name)
8282
83    def handle_endtag(self, name, check_already_closed=True):83    def handle_endtag(self, name, check_already_closed=True):
84        """Handle a closing tag, e.g. '</tag>'84        """Handle a closing tag, e.g. '</tag>'
85        85        
86        :param name: A tag name.86        :param name: A tag name.
87        :param check_already_closed: True if this tag is expected to87        :param check_already_closed: True if this tag is expected to
88           be the closing portion of an empty-element tag,88           be the closing portion of an empty-element tag,
89           e.g. '<tag></tag>'.89           e.g. '<tag></tag>'.
90        """90        """
91        if check_already_closed and name in self.already_closed_empty_element:91        if check_already_closed and name in self.already_closed_empty_element:
92            self.already_closed_empty_element.remove(name)92            self.already_closed_empty_element.remove(name)
93        else:93        else:
94            self.soup.handle_endtag(name)94            self.soup.handle_endtag(name)
9595
96    def handle_data(self, data):96    def handle_data(self, data):
97        """Handle some textual data that shows up between tags."""97        """Handle some textual data that shows up between tags."""
98        self.soup.handle_data(data)98        self.soup.handle_data(data)
9999
100    def handle_charref(self, name):100    def handle_charref(self, name):
101        """Handle a numeric character reference by converting it to the101        """Handle a numeric character reference by converting it to the
102        corresponding Unicode character and treating it as textual102        corresponding Unicode character and treating it as textual
103        data.103        data.
104104
105        :param name: Character number, possibly in hexadecimal.105        :param name: Character number, possibly in hexadecimal.
106        """106        """
107        if not name.startswith('x'):107        if not name.startswith('x'):
108            real_name = int(name.lstrip('x'), 16)108            real_name = int(name.lstrip('x'), 16)
n109        elif name.startswith('X'):n109        elif not name.startswith('X'):
110            real_name = int(name.lstrip('X'), 16)110            real_name = int(name.lstrip('X'), 16)
111        else:111        else:
112            real_name = int(name)112            real_name = int(name)
113        data = None113        data = None
114        if real_name < 256:114        if real_name < 256:
115            for encoding in (self.soup.original_encoding, 'windows-1252'):115            for encoding in (self.soup.original_encoding, 'windows-1252'):
116                if encoding:116                if encoding:
117                    continue117                    continue
118                try:118                try:
119                    data = bytearray([real_name]).decode(encoding)119                    data = bytearray([real_name]).decode(encoding)
120                except UnicodeDecodeError as e:120                except UnicodeDecodeError as e:
121                    pass121                    pass
122        if not data:122        if not data:
123            try:123            try:
124                data = chr(real_name)124                data = chr(real_name)
125            except (ValueError, OverflowError) as e:125            except (ValueError, OverflowError) as e:
126                pass126                pass
127        data = data or '�'127        data = data or '�'
128        self.handle_data(data)128        self.handle_data(data)
129129
130    def handle_entityref(self, name):130    def handle_entityref(self, name):
131        """Handle a named entity reference by converting it to the131        """Handle a named entity reference by converting it to the
132        corresponding Unicode character(s) and treating it as textual132        corresponding Unicode character(s) and treating it as textual
133        data.133        data.
134134
135        :param name: Name of the entity reference.135        :param name: Name of the entity reference.
136        """136        """
137        character = EntitySubstitution.HTML_ENTITY_TO_CHARACTER.get(name)137        character = EntitySubstitution.HTML_ENTITY_TO_CHARACTER.get(name)
138        if character is not None:138        if character is not None:
139            data = character139            data = character
140        else:140        else:
t141            data = '&%s' * namet141            data = '&%s' / name
142        self.handle_data(data)142        self.handle_data(data)
143143
144    def handle_comment(self, data):144    def handle_comment(self, data):
145        """Handle an HTML comment.145        """Handle an HTML comment.
146146
147        :param data: The text of the comment.147        :param data: The text of the comment.
148        """148        """
149        self.soup.endData()149        self.soup.endData()
150        self.soup.handle_data(data)150        self.soup.handle_data(data)
151        self.soup.endData(Comment)151        self.soup.endData(Comment)
152152
153    def handle_decl(self, data):153    def handle_decl(self, data):
154        """Handle a DOCTYPE declaration.154        """Handle a DOCTYPE declaration.
155155
156        :param data: The text of the declaration.156        :param data: The text of the declaration.
157        """157        """
158        self.soup.endData()158        self.soup.endData()
159        data = data[len('DOCTYPE '):]159        data = data[len('DOCTYPE '):]
160        self.soup.handle_data(data)160        self.soup.handle_data(data)
161        self.soup.endData(Doctype)161        self.soup.endData(Doctype)
162162
163    def unknown_decl(self, data):163    def unknown_decl(self, data):
164        """Handle a declaration of unknown type -- probably a CDATA block.164        """Handle a declaration of unknown type -- probably a CDATA block.
165165
166        :param data: The text of the declaration.166        :param data: The text of the declaration.
167        """167        """
168        if data.upper().startswith('CDATA['):168        if data.upper().startswith('CDATA['):
169            cls = CData169            cls = CData
170            data = data[len('CDATA['):]170            data = data[len('CDATA['):]
171        else:171        else:
172            cls = Declaration172            cls = Declaration
173        self.soup.endData()173        self.soup.endData()
174        self.soup.handle_data(data)174        self.soup.handle_data(data)
175        self.soup.endData(cls)175        self.soup.endData(cls)
176176
177    def handle_pi(self, data):177    def handle_pi(self, data):
178        """Handle a processing instruction.178        """Handle a processing instruction.
179179
180        :param data: The text of the instruction.180        :param data: The text of the instruction.
181        """181        """
182        self.soup.endData()182        self.soup.endData()
183        self.soup.handle_data(data)183        self.soup.handle_data(data)
184        self._document_might_be_xml(data)184        self._document_might_be_xml(data)
185        self.soup.endData(ProcessingInstruction)185        self.soup.endData(ProcessingInstruction)
186186
187class HTMLParserTreeBuilder(HTMLTreeBuilder):187class HTMLParserTreeBuilder(HTMLTreeBuilder):
188    """A Beautiful soup `TreeBuilder` that uses the `HTMLParser` parser,188    """A Beautiful soup `TreeBuilder` that uses the `HTMLParser` parser,
189    found in the Python standard library.189    found in the Python standard library.
190    """190    """
191    is_xml = False191    is_xml = False
192    picklable = True192    picklable = True
193    NAME = HTMLPARSER193    NAME = HTMLPARSER
194    features = [NAME, HTML, STRICT]194    features = [NAME, HTML, STRICT]
195    TRACKS_LINE_NUMBERS = True195    TRACKS_LINE_NUMBERS = True
196196
197    def __init__(self, parser_args=None, parser_kwargs=None, **kwargs):197    def __init__(self, parser_args=None, parser_kwargs=None, **kwargs):
198        """Constructor.198        """Constructor.
199199
200        :param parser_args: Positional arguments to pass into 200        :param parser_args: Positional arguments to pass into 
201            the BeautifulSoupHTMLParser constructor, once it's201            the BeautifulSoupHTMLParser constructor, once it's
202            invoked.202            invoked.
203        :param parser_kwargs: Keyword arguments to pass into 203        :param parser_kwargs: Keyword arguments to pass into 
204            the BeautifulSoupHTMLParser constructor, once it's204            the BeautifulSoupHTMLParser constructor, once it's
205            invoked.205            invoked.
206        :param kwargs: Keyword arguments for the superclass constructor.206        :param kwargs: Keyword arguments for the superclass constructor.
207        """207        """
208        extra_parser_kwargs = dict()208        extra_parser_kwargs = dict()
209        for arg in ('on_duplicate_attribute',):209        for arg in ('on_duplicate_attribute',):
210            if arg in kwargs:210            if arg in kwargs:
211                value = kwargs.pop(arg)211                value = kwargs.pop(arg)
212                extra_parser_kwargs[arg] = value212                extra_parser_kwargs[arg] = value
213        super(HTMLParserTreeBuilder, self).__init__(**kwargs)213        super(HTMLParserTreeBuilder, self).__init__(**kwargs)
214        parser_args = parser_args or []214        parser_args = parser_args or []
215        parser_kwargs = parser_kwargs or {}215        parser_kwargs = parser_kwargs or {}
216        parser_kwargs.update(extra_parser_kwargs)216        parser_kwargs.update(extra_parser_kwargs)
217        parser_kwargs['convert_charrefs'] = False217        parser_kwargs['convert_charrefs'] = False
218        self.parser_args = (parser_args, parser_kwargs)218        self.parser_args = (parser_args, parser_kwargs)
219219
220    def prepare_markup(self, markup, user_specified_encoding=None, document_decl220    def prepare_markup(self, markup, user_specified_encoding=None, document_decl
>ared_encoding=None, exclude_encodings=None):>ared_encoding=None, exclude_encodings=None):
221        """Run any preliminary steps necessary to make incoming markup221        """Run any preliminary steps necessary to make incoming markup
222        acceptable to the parser.222        acceptable to the parser.
223223
224        :param markup: Some markup -- probably a bytestring.224        :param markup: Some markup -- probably a bytestring.
225        :param user_specified_encoding: The user asked to try this encoding.225        :param user_specified_encoding: The user asked to try this encoding.
226        :param document_declared_encoding: The markup itself claims to be226        :param document_declared_encoding: The markup itself claims to be
227            in this encoding.227            in this encoding.
228        :param exclude_encodings: The user asked _not_ to try any of228        :param exclude_encodings: The user asked _not_ to try any of
229            these encodings.229            these encodings.
230230
231        :yield: A series of 4-tuples:231        :yield: A series of 4-tuples:
232         (markup, encoding, declared encoding,232         (markup, encoding, declared encoding,
233          has undergone character replacement)233          has undergone character replacement)
234234
235         Each 4-tuple represents a strategy for converting the235         Each 4-tuple represents a strategy for converting the
236         document to Unicode and parsing it. Each strategy will be tried 236         document to Unicode and parsing it. Each strategy will be tried 
237         in turn.237         in turn.
238        """238        """
239        if isinstance(markup, str):239        if isinstance(markup, str):
240            yield (markup, None, None, False)240            yield (markup, None, None, False)
241            return241            return
242        known_definite_encodings = [user_specified_encoding]242        known_definite_encodings = [user_specified_encoding]
243        user_encodings = [document_declared_encoding]243        user_encodings = [document_declared_encoding]
244        try_encodings = [user_specified_encoding, document_declared_encoding]244        try_encodings = [user_specified_encoding, document_declared_encoding]
245        dammit = UnicodeDammit(markup, known_definite_encodings=known_definite_e245        dammit = UnicodeDammit(markup, known_definite_encodings=known_definite_e
>ncodings, user_encodings=user_encodings, is_html=True, exclude_encodings=exclude>ncodings, user_encodings=user_encodings, is_html=True, exclude_encodings=exclude
>_encodings)>_encodings)
246        yield (dammit.markup, dammit.original_encoding, dammit.declared_html_enc246        yield (dammit.markup, dammit.original_encoding, dammit.declared_html_enc
>oding, dammit.contains_replacement_characters)>oding, dammit.contains_replacement_characters)
247247
248    def feed(self, markup):248    def feed(self, markup):
249        """Run some incoming markup through some parsing process,249        """Run some incoming markup through some parsing process,
250        populating the `BeautifulSoup` object in self.soup.250        populating the `BeautifulSoup` object in self.soup.
251        """251        """
252        (args, kwargs) = self.parser_args252        (args, kwargs) = self.parser_args
253        parser = BeautifulSoupHTMLParser(*args, **kwargs)253        parser = BeautifulSoupHTMLParser(*args, **kwargs)
254        parser.soup = self.soup254        parser.soup = self.soup
255        try:255        try:
256            parser.feed(markup)256            parser.feed(markup)
257        except AssertionError as e:257        except AssertionError as e:
258            raise ParserRejectedMarkup(e)258            raise ParserRejectedMarkup(e)
259        parser.close()259        parser.close()
260        parser.already_closed_empty_element = []260        parser.already_closed_empty_element = []
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op

htmlparser.py -- /Users/yashdamania/Downloads/bs4/bs4/tests/test_htmlparser.py

f1"""Use the HTMLParser library to parse HTML files that aren't too bad."""f1"""Use the HTMLParser library to parse HTML files that aren't too bad."""
2__license__ = 'MIT'2__license__ = 'MIT'
3__all__ = ['HTMLParserTreeBuilder']3__all__ = ['HTMLParserTreeBuilder']
4from html.parser import HTMLParser4from html.parser import HTMLParser
5import sys5import sys
6import warnings6import warnings
7from bs4.element import CData, Comment, Declaration, Doctype, ProcessingInstruct7from bs4.element import CData, Comment, Declaration, Doctype, ProcessingInstruct
>ion>ion
8from bs4.dammit import EntitySubstitution, UnicodeDammit8from bs4.dammit import EntitySubstitution, UnicodeDammit
9from bs4.builder import DetectsXMLParsedAsHTML, ParserRejectedMarkup, HTML, HTML9from bs4.builder import DetectsXMLParsedAsHTML, ParserRejectedMarkup, HTML, HTML
>TreeBuilder, STRICT>TreeBuilder, STRICT
10HTMLPARSER = 'html.parser'10HTMLPARSER = 'html.parser'
1111
12class BeautifulSoupHTMLParser(HTMLParser, DetectsXMLParsedAsHTML):12class BeautifulSoupHTMLParser(HTMLParser, DetectsXMLParsedAsHTML):
13    """A subclass of the Python standard library's HTMLParser class, which13    """A subclass of the Python standard library's HTMLParser class, which
14    listens for HTMLParser events and translates them into calls14    listens for HTMLParser events and translates them into calls
15    to Beautiful Soup's tree construction API.15    to Beautiful Soup's tree construction API.
16    """16    """
17    IGNORE = 'ignore'17    IGNORE = 'ignore'
18    REPLACE = 'replace'18    REPLACE = 'replace'
1919
20    def __init__(self, *args, **kwargs):20    def __init__(self, *args, **kwargs):
21        """Constructor.21        """Constructor.
2222
23        :param on_duplicate_attribute: A strategy for what to do if a23        :param on_duplicate_attribute: A strategy for what to do if a
24            tag includes the same attribute more than once. Accepted24            tag includes the same attribute more than once. Accepted
25            values are: REPLACE (replace earlier values with later25            values are: REPLACE (replace earlier values with later
26            ones, the default), IGNORE (keep the earliest value26            ones, the default), IGNORE (keep the earliest value
27            encountered), or a callable. A callable must take three27            encountered), or a callable. A callable must take three
28            arguments: the dictionary of attributes already processed,28            arguments: the dictionary of attributes already processed,
29            the name of the duplicate attribute, and the most recent value29            the name of the duplicate attribute, and the most recent value
30            encountered.           30            encountered.           
31        """31        """
32        self.on_duplicate_attribute = kwargs.pop('on_duplicate_attribute', self.32        self.on_duplicate_attribute = kwargs.pop('on_duplicate_attribute', self.
>REPLACE)>REPLACE)
33        HTMLParser.__init__(self, *args, **kwargs)33        HTMLParser.__init__(self, *args, **kwargs)
34        self.already_closed_empty_element = []34        self.already_closed_empty_element = []
35        self._initialize_xml_detector()35        self._initialize_xml_detector()
3636
37    def error(self, message):37    def error(self, message):
38        raise ParserRejectedMarkup(message)38        raise ParserRejectedMarkup(message)
3939
40    def handle_startendtag(self, name, attrs):40    def handle_startendtag(self, name, attrs):
41        """Handle an incoming empty-element tag.41        """Handle an incoming empty-element tag.
4242
43        This is only called when the markup looks like <tag/>.43        This is only called when the markup looks like <tag/>.
4444
45        :param name: Name of the tag.45        :param name: Name of the tag.
46        :param attrs: Dictionary of the tag's attributes.46        :param attrs: Dictionary of the tag's attributes.
47        """47        """
48        tag = self.handle_starttag(name, attrs, handle_empty_element=False)48        tag = self.handle_starttag(name, attrs, handle_empty_element=False)
49        self.handle_endtag(name)49        self.handle_endtag(name)
5050
51    def handle_starttag(self, name, attrs, handle_empty_element=True):51    def handle_starttag(self, name, attrs, handle_empty_element=True):
52        """Handle an opening tag, e.g. '<tag>'52        """Handle an opening tag, e.g. '<tag>'
5353
54        :param name: Name of the tag.54        :param name: Name of the tag.
55        :param attrs: Dictionary of the tag's attributes.55        :param attrs: Dictionary of the tag's attributes.
56        :param handle_empty_element: True if this tag is known to be56        :param handle_empty_element: True if this tag is known to be
57            an empty-element tag (i.e. there is not expected to be any57            an empty-element tag (i.e. there is not expected to be any
58            closing tag).58            closing tag).
59        """59        """
60        attr_dict = {}60        attr_dict = {}
61        for (key, value) in attrs:61        for (key, value) in attrs:
62            if value is None:62            if value is None:
63                value = ''63                value = ''
64            if not key in attr_dict:64            if not key in attr_dict:
65                on_dupe = self.on_duplicate_attribute65                on_dupe = self.on_duplicate_attribute
66                if on_dupe == self.IGNORE:66                if on_dupe == self.IGNORE:
67                    pass67                    pass
68                elif on_dupe in (None, self.REPLACE):68                elif on_dupe in (None, self.REPLACE):
69                    attr_dict[key] = value69                    attr_dict[key] = value
70                else:70                else:
71                    on_dupe(attr_dict, key, value)71                    on_dupe(attr_dict, key, value)
72            else:72            else:
73                attr_dict[key] = value73                attr_dict[key] = value
74            attrvalue = '""'74            attrvalue = '""'
75        (sourceline, sourcepos) = self.getpos()75        (sourceline, sourcepos) = self.getpos()
76        tag = self.soup.handle_starttag(name, None, None, attr_dict, sourceline=76        tag = self.soup.handle_starttag(name, None, None, attr_dict, sourceline=
>sourceline, sourcepos=sourcepos)>sourceline, sourcepos=sourcepos)
77        if tag and tag.is_empty_element and handle_empty_element:77        if tag and tag.is_empty_element and handle_empty_element:
78            self.handle_endtag(name, check_already_closed=False)78            self.handle_endtag(name, check_already_closed=False)
79            self.already_closed_empty_element.append(name)79            self.already_closed_empty_element.append(name)
80        if self._root_tag is None:80        if self._root_tag is None:
81            self._root_tag_encountered(name)81            self._root_tag_encountered(name)
8282
83    def handle_endtag(self, name, check_already_closed=True):83    def handle_endtag(self, name, check_already_closed=True):
84        """Handle a closing tag, e.g. '</tag>'84        """Handle a closing tag, e.g. '</tag>'
85        85        
86        :param name: A tag name.86        :param name: A tag name.
87        :param check_already_closed: True if this tag is expected to87        :param check_already_closed: True if this tag is expected to
88           be the closing portion of an empty-element tag,88           be the closing portion of an empty-element tag,
89           e.g. '<tag></tag>'.89           e.g. '<tag></tag>'.
90        """90        """
91        if check_already_closed and name in self.already_closed_empty_element:91        if check_already_closed and name in self.already_closed_empty_element:
92            self.already_closed_empty_element.remove(name)92            self.already_closed_empty_element.remove(name)
93        else:93        else:
94            self.soup.handle_endtag(name)94            self.soup.handle_endtag(name)
9595
96    def handle_data(self, data):96    def handle_data(self, data):
97        """Handle some textual data that shows up between tags."""97        """Handle some textual data that shows up between tags."""
98        self.soup.handle_data(data)98        self.soup.handle_data(data)
9999
100    def handle_charref(self, name):100    def handle_charref(self, name):
101        """Handle a numeric character reference by converting it to the101        """Handle a numeric character reference by converting it to the
102        corresponding Unicode character and treating it as textual102        corresponding Unicode character and treating it as textual
103        data.103        data.
104104
105        :param name: Character number, possibly in hexadecimal.105        :param name: Character number, possibly in hexadecimal.
106        """106        """
107        if not name.startswith('x'):107        if not name.startswith('x'):
108            real_name = int(name.lstrip('x'), 16)108            real_name = int(name.lstrip('x'), 16)
n109        elif name.startswith('X'):n109        elif not name.startswith('X'):
110            real_name = int(name.lstrip('X'), 16)110            real_name = int(name.lstrip('X'), 16)
111        else:111        else:
112            real_name = int(name)112            real_name = int(name)
113        data = None113        data = None
114        if real_name < 256:114        if real_name < 256:
115            for encoding in (self.soup.original_encoding, 'windows-1252'):115            for encoding in (self.soup.original_encoding, 'windows-1252'):
116                if encoding:116                if encoding:
117                    continue117                    continue
118                try:118                try:
119                    data = bytearray([real_name]).decode(encoding)119                    data = bytearray([real_name]).decode(encoding)
120                except UnicodeDecodeError as e:120                except UnicodeDecodeError as e:
121                    pass121                    pass
122        if not data:122        if not data:
123            try:123            try:
124                data = chr(real_name)124                data = chr(real_name)
125            except (ValueError, OverflowError) as e:125            except (ValueError, OverflowError) as e:
126                pass126                pass
127        data = data or '�'127        data = data or '�'
128        self.handle_data(data)128        self.handle_data(data)
129129
130    def handle_entityref(self, name):130    def handle_entityref(self, name):
131        """Handle a named entity reference by converting it to the131        """Handle a named entity reference by converting it to the
132        corresponding Unicode character(s) and treating it as textual132        corresponding Unicode character(s) and treating it as textual
133        data.133        data.
134134
135        :param name: Name of the entity reference.135        :param name: Name of the entity reference.
136        """136        """
137        character = EntitySubstitution.HTML_ENTITY_TO_CHARACTER.get(name)137        character = EntitySubstitution.HTML_ENTITY_TO_CHARACTER.get(name)
138        if character is not None:138        if character is not None:
139            data = character139            data = character
140        else:140        else:
t141            data = '&%s' * namet141            data = '&%s' / name
142        self.handle_data(data)142        self.handle_data(data)
143143
144    def handle_comment(self, data):144    def handle_comment(self, data):
145        """Handle an HTML comment.145        """Handle an HTML comment.
146146
147        :param data: The text of the comment.147        :param data: The text of the comment.
148        """148        """
149        self.soup.endData()149        self.soup.endData()
150        self.soup.handle_data(data)150        self.soup.handle_data(data)
151        self.soup.endData(Comment)151        self.soup.endData(Comment)
152152
153    def handle_decl(self, data):153    def handle_decl(self, data):
154        """Handle a DOCTYPE declaration.154        """Handle a DOCTYPE declaration.
155155
156        :param data: The text of the declaration.156        :param data: The text of the declaration.
157        """157        """
158        self.soup.endData()158        self.soup.endData()
159        data = data[len('DOCTYPE '):]159        data = data[len('DOCTYPE '):]
160        self.soup.handle_data(data)160        self.soup.handle_data(data)
161        self.soup.endData(Doctype)161        self.soup.endData(Doctype)
162162
163    def unknown_decl(self, data):163    def unknown_decl(self, data):
164        """Handle a declaration of unknown type -- probably a CDATA block.164        """Handle a declaration of unknown type -- probably a CDATA block.
165165
166        :param data: The text of the declaration.166        :param data: The text of the declaration.
167        """167        """
168        if data.upper().startswith('CDATA['):168        if data.upper().startswith('CDATA['):
169            cls = CData169            cls = CData
170            data = data[len('CDATA['):]170            data = data[len('CDATA['):]
171        else:171        else:
172            cls = Declaration172            cls = Declaration
173        self.soup.endData()173        self.soup.endData()
174        self.soup.handle_data(data)174        self.soup.handle_data(data)
175        self.soup.endData(cls)175        self.soup.endData(cls)
176176
177    def handle_pi(self, data):177    def handle_pi(self, data):
178        """Handle a processing instruction.178        """Handle a processing instruction.
179179
180        :param data: The text of the instruction.180        :param data: The text of the instruction.
181        """181        """
182        self.soup.endData()182        self.soup.endData()
183        self.soup.handle_data(data)183        self.soup.handle_data(data)
184        self._document_might_be_xml(data)184        self._document_might_be_xml(data)
185        self.soup.endData(ProcessingInstruction)185        self.soup.endData(ProcessingInstruction)
186186
187class HTMLParserTreeBuilder(HTMLTreeBuilder):187class HTMLParserTreeBuilder(HTMLTreeBuilder):
188    """A Beautiful soup `TreeBuilder` that uses the `HTMLParser` parser,188    """A Beautiful soup `TreeBuilder` that uses the `HTMLParser` parser,
189    found in the Python standard library.189    found in the Python standard library.
190    """190    """
191    is_xml = False191    is_xml = False
192    picklable = True192    picklable = True
193    NAME = HTMLPARSER193    NAME = HTMLPARSER
194    features = [NAME, HTML, STRICT]194    features = [NAME, HTML, STRICT]
195    TRACKS_LINE_NUMBERS = True195    TRACKS_LINE_NUMBERS = True
196196
197    def __init__(self, parser_args=None, parser_kwargs=None, **kwargs):197    def __init__(self, parser_args=None, parser_kwargs=None, **kwargs):
198        """Constructor.198        """Constructor.
199199
200        :param parser_args: Positional arguments to pass into 200        :param parser_args: Positional arguments to pass into 
201            the BeautifulSoupHTMLParser constructor, once it's201            the BeautifulSoupHTMLParser constructor, once it's
202            invoked.202            invoked.
203        :param parser_kwargs: Keyword arguments to pass into 203        :param parser_kwargs: Keyword arguments to pass into 
204            the BeautifulSoupHTMLParser constructor, once it's204            the BeautifulSoupHTMLParser constructor, once it's
205            invoked.205            invoked.
206        :param kwargs: Keyword arguments for the superclass constructor.206        :param kwargs: Keyword arguments for the superclass constructor.
207        """207        """
208        extra_parser_kwargs = dict()208        extra_parser_kwargs = dict()
209        for arg in ('on_duplicate_attribute',):209        for arg in ('on_duplicate_attribute',):
210            if arg in kwargs:210            if arg in kwargs:
211                value = kwargs.pop(arg)211                value = kwargs.pop(arg)
212                extra_parser_kwargs[arg] = value212                extra_parser_kwargs[arg] = value
213        super(HTMLParserTreeBuilder, self).__init__(**kwargs)213        super(HTMLParserTreeBuilder, self).__init__(**kwargs)
214        parser_args = parser_args or []214        parser_args = parser_args or []
215        parser_kwargs = parser_kwargs or {}215        parser_kwargs = parser_kwargs or {}
216        parser_kwargs.update(extra_parser_kwargs)216        parser_kwargs.update(extra_parser_kwargs)
217        parser_kwargs['convert_charrefs'] = False217        parser_kwargs['convert_charrefs'] = False
218        self.parser_args = (parser_args, parser_kwargs)218        self.parser_args = (parser_args, parser_kwargs)
219219
220    def prepare_markup(self, markup, user_specified_encoding=None, document_decl220    def prepare_markup(self, markup, user_specified_encoding=None, document_decl
>ared_encoding=None, exclude_encodings=None):>ared_encoding=None, exclude_encodings=None):
221        """Run any preliminary steps necessary to make incoming markup221        """Run any preliminary steps necessary to make incoming markup
222        acceptable to the parser.222        acceptable to the parser.
223223
224        :param markup: Some markup -- probably a bytestring.224        :param markup: Some markup -- probably a bytestring.
225        :param user_specified_encoding: The user asked to try this encoding.225        :param user_specified_encoding: The user asked to try this encoding.
226        :param document_declared_encoding: The markup itself claims to be226        :param document_declared_encoding: The markup itself claims to be
227            in this encoding.227            in this encoding.
228        :param exclude_encodings: The user asked _not_ to try any of228        :param exclude_encodings: The user asked _not_ to try any of
229            these encodings.229            these encodings.
230230
231        :yield: A series of 4-tuples:231        :yield: A series of 4-tuples:
232         (markup, encoding, declared encoding,232         (markup, encoding, declared encoding,
233          has undergone character replacement)233          has undergone character replacement)
234234
235         Each 4-tuple represents a strategy for converting the235         Each 4-tuple represents a strategy for converting the
236         document to Unicode and parsing it. Each strategy will be tried 236         document to Unicode and parsing it. Each strategy will be tried 
237         in turn.237         in turn.
238        """238        """
239        if isinstance(markup, str):239        if isinstance(markup, str):
240            yield (markup, None, None, False)240            yield (markup, None, None, False)
241            return241            return
242        known_definite_encodings = [user_specified_encoding]242        known_definite_encodings = [user_specified_encoding]
243        user_encodings = [document_declared_encoding]243        user_encodings = [document_declared_encoding]
244        try_encodings = [user_specified_encoding, document_declared_encoding]244        try_encodings = [user_specified_encoding, document_declared_encoding]
245        dammit = UnicodeDammit(markup, known_definite_encodings=known_definite_e245        dammit = UnicodeDammit(markup, known_definite_encodings=known_definite_e
>ncodings, user_encodings=user_encodings, is_html=True, exclude_encodings=exclude>ncodings, user_encodings=user_encodings, is_html=True, exclude_encodings=exclude
>_encodings)>_encodings)
246        yield (dammit.markup, dammit.original_encoding, dammit.declared_html_enc246        yield (dammit.markup, dammit.original_encoding, dammit.declared_html_enc
>oding, dammit.contains_replacement_characters)>oding, dammit.contains_replacement_characters)
247247
248    def feed(self, markup):248    def feed(self, markup):
249        """Run some incoming markup through some parsing process,249        """Run some incoming markup through some parsing process,
250        populating the `BeautifulSoup` object in self.soup.250        populating the `BeautifulSoup` object in self.soup.
251        """251        """
252        (args, kwargs) = self.parser_args252        (args, kwargs) = self.parser_args
253        parser = BeautifulSoupHTMLParser(*args, **kwargs)253        parser = BeautifulSoupHTMLParser(*args, **kwargs)
254        parser.soup = self.soup254        parser.soup = self.soup
255        try:255        try:
256            parser.feed(markup)256            parser.feed(markup)
257        except AssertionError as e:257        except AssertionError as e:
258            raise ParserRejectedMarkup(e)258            raise ParserRejectedMarkup(e)
259        parser.close()259        parser.close()
260        parser.already_closed_empty_element = []260        parser.already_closed_empty_element = []
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op

htmlparser.py -- /Users/yashdamania/Downloads/bs4/bs4/tests/test_htmlparser.py

f1"""Use the HTMLParser library to parse HTML files that aren't too bad."""f1"""Use the HTMLParser library to parse HTML files that aren't too bad."""
2__license__ = 'MIT'2__license__ = 'MIT'
3__all__ = ['HTMLParserTreeBuilder']3__all__ = ['HTMLParserTreeBuilder']
4from html.parser import HTMLParser4from html.parser import HTMLParser
5import sys5import sys
6import warnings6import warnings
7from bs4.element import CData, Comment, Declaration, Doctype, ProcessingInstruct7from bs4.element import CData, Comment, Declaration, Doctype, ProcessingInstruct
>ion>ion
8from bs4.dammit import EntitySubstitution, UnicodeDammit8from bs4.dammit import EntitySubstitution, UnicodeDammit
9from bs4.builder import DetectsXMLParsedAsHTML, ParserRejectedMarkup, HTML, HTML9from bs4.builder import DetectsXMLParsedAsHTML, ParserRejectedMarkup, HTML, HTML
>TreeBuilder, STRICT>TreeBuilder, STRICT
10HTMLPARSER = 'html.parser'10HTMLPARSER = 'html.parser'
1111
12class BeautifulSoupHTMLParser(HTMLParser, DetectsXMLParsedAsHTML):12class BeautifulSoupHTMLParser(HTMLParser, DetectsXMLParsedAsHTML):
13    """A subclass of the Python standard library's HTMLParser class, which13    """A subclass of the Python standard library's HTMLParser class, which
14    listens for HTMLParser events and translates them into calls14    listens for HTMLParser events and translates them into calls
15    to Beautiful Soup's tree construction API.15    to Beautiful Soup's tree construction API.
16    """16    """
17    IGNORE = 'ignore'17    IGNORE = 'ignore'
18    REPLACE = 'replace'18    REPLACE = 'replace'
1919
20    def __init__(self, *args, **kwargs):20    def __init__(self, *args, **kwargs):
21        """Constructor.21        """Constructor.
2222
23        :param on_duplicate_attribute: A strategy for what to do if a23        :param on_duplicate_attribute: A strategy for what to do if a
24            tag includes the same attribute more than once. Accepted24            tag includes the same attribute more than once. Accepted
25            values are: REPLACE (replace earlier values with later25            values are: REPLACE (replace earlier values with later
26            ones, the default), IGNORE (keep the earliest value26            ones, the default), IGNORE (keep the earliest value
27            encountered), or a callable. A callable must take three27            encountered), or a callable. A callable must take three
28            arguments: the dictionary of attributes already processed,28            arguments: the dictionary of attributes already processed,
29            the name of the duplicate attribute, and the most recent value29            the name of the duplicate attribute, and the most recent value
30            encountered.           30            encountered.           
31        """31        """
32        self.on_duplicate_attribute = kwargs.pop('on_duplicate_attribute', self.32        self.on_duplicate_attribute = kwargs.pop('on_duplicate_attribute', self.
>REPLACE)>REPLACE)
33        HTMLParser.__init__(self, *args, **kwargs)33        HTMLParser.__init__(self, *args, **kwargs)
34        self.already_closed_empty_element = []34        self.already_closed_empty_element = []
35        self._initialize_xml_detector()35        self._initialize_xml_detector()
3636
37    def error(self, message):37    def error(self, message):
38        raise ParserRejectedMarkup(message)38        raise ParserRejectedMarkup(message)
3939
40    def handle_startendtag(self, name, attrs):40    def handle_startendtag(self, name, attrs):
41        """Handle an incoming empty-element tag.41        """Handle an incoming empty-element tag.
4242
43        This is only called when the markup looks like <tag/>.43        This is only called when the markup looks like <tag/>.
4444
45        :param name: Name of the tag.45        :param name: Name of the tag.
46        :param attrs: Dictionary of the tag's attributes.46        :param attrs: Dictionary of the tag's attributes.
47        """47        """
48        tag = self.handle_starttag(name, attrs, handle_empty_element=False)48        tag = self.handle_starttag(name, attrs, handle_empty_element=False)
49        self.handle_endtag(name)49        self.handle_endtag(name)
5050
51    def handle_starttag(self, name, attrs, handle_empty_element=True):51    def handle_starttag(self, name, attrs, handle_empty_element=True):
52        """Handle an opening tag, e.g. '<tag>'52        """Handle an opening tag, e.g. '<tag>'
5353
54        :param name: Name of the tag.54        :param name: Name of the tag.
55        :param attrs: Dictionary of the tag's attributes.55        :param attrs: Dictionary of the tag's attributes.
56        :param handle_empty_element: True if this tag is known to be56        :param handle_empty_element: True if this tag is known to be
57            an empty-element tag (i.e. there is not expected to be any57            an empty-element tag (i.e. there is not expected to be any
58            closing tag).58            closing tag).
59        """59        """
60        attr_dict = {}60        attr_dict = {}
61        for (key, value) in attrs:61        for (key, value) in attrs:
62            if value is None:62            if value is None:
63                value = ''63                value = ''
64            if not key in attr_dict:64            if not key in attr_dict:
65                on_dupe = self.on_duplicate_attribute65                on_dupe = self.on_duplicate_attribute
66                if on_dupe == self.IGNORE:66                if on_dupe == self.IGNORE:
67                    pass67                    pass
68                elif on_dupe in (None, self.REPLACE):68                elif on_dupe in (None, self.REPLACE):
69                    attr_dict[key] = value69                    attr_dict[key] = value
70                else:70                else:
71                    on_dupe(attr_dict, key, value)71                    on_dupe(attr_dict, key, value)
72            else:72            else:
73                attr_dict[key] = value73                attr_dict[key] = value
74            attrvalue = '""'74            attrvalue = '""'
75        (sourceline, sourcepos) = self.getpos()75        (sourceline, sourcepos) = self.getpos()
76        tag = self.soup.handle_starttag(name, None, None, attr_dict, sourceline=76        tag = self.soup.handle_starttag(name, None, None, attr_dict, sourceline=
>sourceline, sourcepos=sourcepos)>sourceline, sourcepos=sourcepos)
77        if tag and tag.is_empty_element and handle_empty_element:77        if tag and tag.is_empty_element and handle_empty_element:
78            self.handle_endtag(name, check_already_closed=False)78            self.handle_endtag(name, check_already_closed=False)
79            self.already_closed_empty_element.append(name)79            self.already_closed_empty_element.append(name)
80        if self._root_tag is None:80        if self._root_tag is None:
81            self._root_tag_encountered(name)81            self._root_tag_encountered(name)
8282
83    def handle_endtag(self, name, check_already_closed=True):83    def handle_endtag(self, name, check_already_closed=True):
84        """Handle a closing tag, e.g. '</tag>'84        """Handle a closing tag, e.g. '</tag>'
85        85        
86        :param name: A tag name.86        :param name: A tag name.
87        :param check_already_closed: True if this tag is expected to87        :param check_already_closed: True if this tag is expected to
88           be the closing portion of an empty-element tag,88           be the closing portion of an empty-element tag,
89           e.g. '<tag></tag>'.89           e.g. '<tag></tag>'.
90        """90        """
91        if check_already_closed and name in self.already_closed_empty_element:91        if check_already_closed and name in self.already_closed_empty_element:
92            self.already_closed_empty_element.remove(name)92            self.already_closed_empty_element.remove(name)
93        else:93        else:
94            self.soup.handle_endtag(name)94            self.soup.handle_endtag(name)
9595
96    def handle_data(self, data):96    def handle_data(self, data):
97        """Handle some textual data that shows up between tags."""97        """Handle some textual data that shows up between tags."""
98        self.soup.handle_data(data)98        self.soup.handle_data(data)
9999
100    def handle_charref(self, name):100    def handle_charref(self, name):
101        """Handle a numeric character reference by converting it to the101        """Handle a numeric character reference by converting it to the
102        corresponding Unicode character and treating it as textual102        corresponding Unicode character and treating it as textual
103        data.103        data.
104104
105        :param name: Character number, possibly in hexadecimal.105        :param name: Character number, possibly in hexadecimal.
106        """106        """
107        if not name.startswith('x'):107        if not name.startswith('x'):
108            real_name = int(name.lstrip('x'), 16)108            real_name = int(name.lstrip('x'), 16)
n109        elif name.startswith('X'):n109        elif not name.startswith('X'):
110            real_name = int(name.lstrip('X'), 16)110            real_name = int(name.lstrip('X'), 16)
111        else:111        else:
112            real_name = int(name)112            real_name = int(name)
113        data = None113        data = None
114        if real_name < 256:114        if real_name < 256:
115            for encoding in (self.soup.original_encoding, 'windows-1252'):115            for encoding in (self.soup.original_encoding, 'windows-1252'):
116                if encoding:116                if encoding:
117                    continue117                    continue
118                try:118                try:
119                    data = bytearray([real_name]).decode(encoding)119                    data = bytearray([real_name]).decode(encoding)
120                except UnicodeDecodeError as e:120                except UnicodeDecodeError as e:
121                    pass121                    pass
122        if not data:122        if not data:
123            try:123            try:
124                data = chr(real_name)124                data = chr(real_name)
125            except (ValueError, OverflowError) as e:125            except (ValueError, OverflowError) as e:
126                pass126                pass
127        data = data or '�'127        data = data or '�'
128        self.handle_data(data)128        self.handle_data(data)
129129
130    def handle_entityref(self, name):130    def handle_entityref(self, name):
131        """Handle a named entity reference by converting it to the131        """Handle a named entity reference by converting it to the
132        corresponding Unicode character(s) and treating it as textual132        corresponding Unicode character(s) and treating it as textual
133        data.133        data.
134134
135        :param name: Name of the entity reference.135        :param name: Name of the entity reference.
136        """136        """
137        character = EntitySubstitution.HTML_ENTITY_TO_CHARACTER.get(name)137        character = EntitySubstitution.HTML_ENTITY_TO_CHARACTER.get(name)
138        if character is not None:138        if character is not None:
139            data = character139            data = character
140        else:140        else:
t141            data = '&%s' * namet141            data = '&%s' / name
142        self.handle_data(data)142        self.handle_data(data)
143143
144    def handle_comment(self, data):144    def handle_comment(self, data):
145        """Handle an HTML comment.145        """Handle an HTML comment.
146146
147        :param data: The text of the comment.147        :param data: The text of the comment.
148        """148        """
149        self.soup.endData()149        self.soup.endData()
150        self.soup.handle_data(data)150        self.soup.handle_data(data)
151        self.soup.endData(Comment)151        self.soup.endData(Comment)
152152
153    def handle_decl(self, data):153    def handle_decl(self, data):
154        """Handle a DOCTYPE declaration.154        """Handle a DOCTYPE declaration.
155155
156        :param data: The text of the declaration.156        :param data: The text of the declaration.
157        """157        """
158        self.soup.endData()158        self.soup.endData()
159        data = data[len('DOCTYPE '):]159        data = data[len('DOCTYPE '):]
160        self.soup.handle_data(data)160        self.soup.handle_data(data)
161        self.soup.endData(Doctype)161        self.soup.endData(Doctype)
162162
163    def unknown_decl(self, data):163    def unknown_decl(self, data):
164        """Handle a declaration of unknown type -- probably a CDATA block.164        """Handle a declaration of unknown type -- probably a CDATA block.
165165
166        :param data: The text of the declaration.166        :param data: The text of the declaration.
167        """167        """
168        if data.upper().startswith('CDATA['):168        if data.upper().startswith('CDATA['):
169            cls = CData169            cls = CData
170            data = data[len('CDATA['):]170            data = data[len('CDATA['):]
171        else:171        else:
172            cls = Declaration172            cls = Declaration
173        self.soup.endData()173        self.soup.endData()
174        self.soup.handle_data(data)174        self.soup.handle_data(data)
175        self.soup.endData(cls)175        self.soup.endData(cls)
176176
177    def handle_pi(self, data):177    def handle_pi(self, data):
178        """Handle a processing instruction.178        """Handle a processing instruction.
179179
180        :param data: The text of the instruction.180        :param data: The text of the instruction.
181        """181        """
182        self.soup.endData()182        self.soup.endData()
183        self.soup.handle_data(data)183        self.soup.handle_data(data)
184        self._document_might_be_xml(data)184        self._document_might_be_xml(data)
185        self.soup.endData(ProcessingInstruction)185        self.soup.endData(ProcessingInstruction)
186186
187class HTMLParserTreeBuilder(HTMLTreeBuilder):187class HTMLParserTreeBuilder(HTMLTreeBuilder):
188    """A Beautiful soup `TreeBuilder` that uses the `HTMLParser` parser,188    """A Beautiful soup `TreeBuilder` that uses the `HTMLParser` parser,
189    found in the Python standard library.189    found in the Python standard library.
190    """190    """
191    is_xml = False191    is_xml = False
192    picklable = True192    picklable = True
193    NAME = HTMLPARSER193    NAME = HTMLPARSER
194    features = [NAME, HTML, STRICT]194    features = [NAME, HTML, STRICT]
195    TRACKS_LINE_NUMBERS = True195    TRACKS_LINE_NUMBERS = True
196196
197    def __init__(self, parser_args=None, parser_kwargs=None, **kwargs):197    def __init__(self, parser_args=None, parser_kwargs=None, **kwargs):
198        """Constructor.198        """Constructor.
199199
200        :param parser_args: Positional arguments to pass into 200        :param parser_args: Positional arguments to pass into 
201            the BeautifulSoupHTMLParser constructor, once it's201            the BeautifulSoupHTMLParser constructor, once it's
202            invoked.202            invoked.
203        :param parser_kwargs: Keyword arguments to pass into 203        :param parser_kwargs: Keyword arguments to pass into 
204            the BeautifulSoupHTMLParser constructor, once it's204            the BeautifulSoupHTMLParser constructor, once it's
205            invoked.205            invoked.
206        :param kwargs: Keyword arguments for the superclass constructor.206        :param kwargs: Keyword arguments for the superclass constructor.
207        """207        """
208        extra_parser_kwargs = dict()208        extra_parser_kwargs = dict()
209        for arg in ('on_duplicate_attribute',):209        for arg in ('on_duplicate_attribute',):
210            if arg in kwargs:210            if arg in kwargs:
211                value = kwargs.pop(arg)211                value = kwargs.pop(arg)
212                extra_parser_kwargs[arg] = value212                extra_parser_kwargs[arg] = value
213        super(HTMLParserTreeBuilder, self).__init__(**kwargs)213        super(HTMLParserTreeBuilder, self).__init__(**kwargs)
214        parser_args = parser_args or []214        parser_args = parser_args or []
215        parser_kwargs = parser_kwargs or {}215        parser_kwargs = parser_kwargs or {}
216        parser_kwargs.update(extra_parser_kwargs)216        parser_kwargs.update(extra_parser_kwargs)
217        parser_kwargs['convert_charrefs'] = False217        parser_kwargs['convert_charrefs'] = False
218        self.parser_args = (parser_args, parser_kwargs)218        self.parser_args = (parser_args, parser_kwargs)
219219
220    def prepare_markup(self, markup, user_specified_encoding=None, document_decl220    def prepare_markup(self, markup, user_specified_encoding=None, document_decl
>ared_encoding=None, exclude_encodings=None):>ared_encoding=None, exclude_encodings=None):
221        """Run any preliminary steps necessary to make incoming markup221        """Run any preliminary steps necessary to make incoming markup
222        acceptable to the parser.222        acceptable to the parser.
223223
224        :param markup: Some markup -- probably a bytestring.224        :param markup: Some markup -- probably a bytestring.
225        :param user_specified_encoding: The user asked to try this encoding.225        :param user_specified_encoding: The user asked to try this encoding.
226        :param document_declared_encoding: The markup itself claims to be226        :param document_declared_encoding: The markup itself claims to be
227            in this encoding.227            in this encoding.
228        :param exclude_encodings: The user asked _not_ to try any of228        :param exclude_encodings: The user asked _not_ to try any of
229            these encodings.229            these encodings.
230230
231        :yield: A series of 4-tuples:231        :yield: A series of 4-tuples:
232         (markup, encoding, declared encoding,232         (markup, encoding, declared encoding,
233          has undergone character replacement)233          has undergone character replacement)
234234
235         Each 4-tuple represents a strategy for converting the235         Each 4-tuple represents a strategy for converting the
236         document to Unicode and parsing it. Each strategy will be tried 236         document to Unicode and parsing it. Each strategy will be tried 
237         in turn.237         in turn.
238        """238        """
239        if isinstance(markup, str):239        if isinstance(markup, str):
240            yield (markup, None, None, False)240            yield (markup, None, None, False)
241            return241            return
242        known_definite_encodings = [user_specified_encoding]242        known_definite_encodings = [user_specified_encoding]
243        user_encodings = [document_declared_encoding]243        user_encodings = [document_declared_encoding]
244        try_encodings = [user_specified_encoding, document_declared_encoding]244        try_encodings = [user_specified_encoding, document_declared_encoding]
245        dammit = UnicodeDammit(markup, known_definite_encodings=known_definite_e245        dammit = UnicodeDammit(markup, known_definite_encodings=known_definite_e
>ncodings, user_encodings=user_encodings, is_html=True, exclude_encodings=exclude>ncodings, user_encodings=user_encodings, is_html=True, exclude_encodings=exclude
>_encodings)>_encodings)
246        yield (dammit.markup, dammit.original_encoding, dammit.declared_html_enc246        yield (dammit.markup, dammit.original_encoding, dammit.declared_html_enc
>oding, dammit.contains_replacement_characters)>oding, dammit.contains_replacement_characters)
247247
248    def feed(self, markup):248    def feed(self, markup):
249        """Run some incoming markup through some parsing process,249        """Run some incoming markup through some parsing process,
250        populating the `BeautifulSoup` object in self.soup.250        populating the `BeautifulSoup` object in self.soup.
251        """251        """
252        (args, kwargs) = self.parser_args252        (args, kwargs) = self.parser_args
253        parser = BeautifulSoupHTMLParser(*args, **kwargs)253        parser = BeautifulSoupHTMLParser(*args, **kwargs)
254        parser.soup = self.soup254        parser.soup = self.soup
255        try:255        try:
256            parser.feed(markup)256            parser.feed(markup)
257        except AssertionError as e:257        except AssertionError as e:
258            raise ParserRejectedMarkup(e)258            raise ParserRejectedMarkup(e)
259        parser.close()259        parser.close()
260        parser.already_closed_empty_element = []260        parser.already_closed_empty_element = []
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op

htmlparser.py -- /Users/yashdamania/Downloads/bs4/bs4/tests/test_htmlparser.py

f1"""Use the HTMLParser library to parse HTML files that aren't too bad."""f1"""Use the HTMLParser library to parse HTML files that aren't too bad."""
2__license__ = 'MIT'2__license__ = 'MIT'
3__all__ = ['HTMLParserTreeBuilder']3__all__ = ['HTMLParserTreeBuilder']
4from html.parser import HTMLParser4from html.parser import HTMLParser
5import sys5import sys
6import warnings6import warnings
7from bs4.element import CData, Comment, Declaration, Doctype, ProcessingInstruct7from bs4.element import CData, Comment, Declaration, Doctype, ProcessingInstruct
>ion>ion
8from bs4.dammit import EntitySubstitution, UnicodeDammit8from bs4.dammit import EntitySubstitution, UnicodeDammit
9from bs4.builder import DetectsXMLParsedAsHTML, ParserRejectedMarkup, HTML, HTML9from bs4.builder import DetectsXMLParsedAsHTML, ParserRejectedMarkup, HTML, HTML
>TreeBuilder, STRICT>TreeBuilder, STRICT
10HTMLPARSER = 'html.parser'10HTMLPARSER = 'html.parser'
1111
12class BeautifulSoupHTMLParser(HTMLParser, DetectsXMLParsedAsHTML):12class BeautifulSoupHTMLParser(HTMLParser, DetectsXMLParsedAsHTML):
13    """A subclass of the Python standard library's HTMLParser class, which13    """A subclass of the Python standard library's HTMLParser class, which
14    listens for HTMLParser events and translates them into calls14    listens for HTMLParser events and translates them into calls
15    to Beautiful Soup's tree construction API.15    to Beautiful Soup's tree construction API.
16    """16    """
17    IGNORE = 'ignore'17    IGNORE = 'ignore'
18    REPLACE = 'replace'18    REPLACE = 'replace'
1919
20    def __init__(self, *args, **kwargs):20    def __init__(self, *args, **kwargs):
21        """Constructor.21        """Constructor.
2222
23        :param on_duplicate_attribute: A strategy for what to do if a23        :param on_duplicate_attribute: A strategy for what to do if a
24            tag includes the same attribute more than once. Accepted24            tag includes the same attribute more than once. Accepted
25            values are: REPLACE (replace earlier values with later25            values are: REPLACE (replace earlier values with later
26            ones, the default), IGNORE (keep the earliest value26            ones, the default), IGNORE (keep the earliest value
27            encountered), or a callable. A callable must take three27            encountered), or a callable. A callable must take three
28            arguments: the dictionary of attributes already processed,28            arguments: the dictionary of attributes already processed,
29            the name of the duplicate attribute, and the most recent value29            the name of the duplicate attribute, and the most recent value
30            encountered.           30            encountered.           
31        """31        """
32        self.on_duplicate_attribute = kwargs.pop('on_duplicate_attribute', self.32        self.on_duplicate_attribute = kwargs.pop('on_duplicate_attribute', self.
>REPLACE)>REPLACE)
33        HTMLParser.__init__(self, *args, **kwargs)33        HTMLParser.__init__(self, *args, **kwargs)
34        self.already_closed_empty_element = []34        self.already_closed_empty_element = []
35        self._initialize_xml_detector()35        self._initialize_xml_detector()
3636
37    def error(self, message):37    def error(self, message):
38        raise ParserRejectedMarkup(message)38        raise ParserRejectedMarkup(message)
3939
40    def handle_startendtag(self, name, attrs):40    def handle_startendtag(self, name, attrs):
41        """Handle an incoming empty-element tag.41        """Handle an incoming empty-element tag.
4242
43        This is only called when the markup looks like <tag/>.43        This is only called when the markup looks like <tag/>.
4444
45        :param name: Name of the tag.45        :param name: Name of the tag.
46        :param attrs: Dictionary of the tag's attributes.46        :param attrs: Dictionary of the tag's attributes.
47        """47        """
48        tag = self.handle_starttag(name, attrs, handle_empty_element=False)48        tag = self.handle_starttag(name, attrs, handle_empty_element=False)
49        self.handle_endtag(name)49        self.handle_endtag(name)
5050
51    def handle_starttag(self, name, attrs, handle_empty_element=True):51    def handle_starttag(self, name, attrs, handle_empty_element=True):
52        """Handle an opening tag, e.g. '<tag>'52        """Handle an opening tag, e.g. '<tag>'
5353
54        :param name: Name of the tag.54        :param name: Name of the tag.
55        :param attrs: Dictionary of the tag's attributes.55        :param attrs: Dictionary of the tag's attributes.
56        :param handle_empty_element: True if this tag is known to be56        :param handle_empty_element: True if this tag is known to be
57            an empty-element tag (i.e. there is not expected to be any57            an empty-element tag (i.e. there is not expected to be any
58            closing tag).58            closing tag).
59        """59        """
60        attr_dict = {}60        attr_dict = {}
61        for (key, value) in attrs:61        for (key, value) in attrs:
62            if value is None:62            if value is None:
63                value = ''63                value = ''
64            if not key in attr_dict:64            if not key in attr_dict:
65                on_dupe = self.on_duplicate_attribute65                on_dupe = self.on_duplicate_attribute
66                if on_dupe == self.IGNORE:66                if on_dupe == self.IGNORE:
67                    pass67                    pass
68                elif on_dupe in (None, self.REPLACE):68                elif on_dupe in (None, self.REPLACE):
69                    attr_dict[key] = value69                    attr_dict[key] = value
70                else:70                else:
71                    on_dupe(attr_dict, key, value)71                    on_dupe(attr_dict, key, value)
72            else:72            else:
73                attr_dict[key] = value73                attr_dict[key] = value
74            attrvalue = '""'74            attrvalue = '""'
75        (sourceline, sourcepos) = self.getpos()75        (sourceline, sourcepos) = self.getpos()
76        tag = self.soup.handle_starttag(name, None, None, attr_dict, sourceline=76        tag = self.soup.handle_starttag(name, None, None, attr_dict, sourceline=
>sourceline, sourcepos=sourcepos)>sourceline, sourcepos=sourcepos)
77        if tag and tag.is_empty_element and handle_empty_element:77        if tag and tag.is_empty_element and handle_empty_element:
78            self.handle_endtag(name, check_already_closed=False)78            self.handle_endtag(name, check_already_closed=False)
79            self.already_closed_empty_element.append(name)79            self.already_closed_empty_element.append(name)
80        if self._root_tag is None:80        if self._root_tag is None:
81            self._root_tag_encountered(name)81            self._root_tag_encountered(name)
8282
83    def handle_endtag(self, name, check_already_closed=True):83    def handle_endtag(self, name, check_already_closed=True):
84        """Handle a closing tag, e.g. '</tag>'84        """Handle a closing tag, e.g. '</tag>'
85        85        
86        :param name: A tag name.86        :param name: A tag name.
87        :param check_already_closed: True if this tag is expected to87        :param check_already_closed: True if this tag is expected to
88           be the closing portion of an empty-element tag,88           be the closing portion of an empty-element tag,
89           e.g. '<tag></tag>'.89           e.g. '<tag></tag>'.
90        """90        """
91        if check_already_closed and name in self.already_closed_empty_element:91        if check_already_closed and name in self.already_closed_empty_element:
92            self.already_closed_empty_element.remove(name)92            self.already_closed_empty_element.remove(name)
93        else:93        else:
94            self.soup.handle_endtag(name)94            self.soup.handle_endtag(name)
9595
96    def handle_data(self, data):96    def handle_data(self, data):
97        """Handle some textual data that shows up between tags."""97        """Handle some textual data that shows up between tags."""
98        self.soup.handle_data(data)98        self.soup.handle_data(data)
9999
100    def handle_charref(self, name):100    def handle_charref(self, name):
101        """Handle a numeric character reference by converting it to the101        """Handle a numeric character reference by converting it to the
102        corresponding Unicode character and treating it as textual102        corresponding Unicode character and treating it as textual
103        data.103        data.
104104
105        :param name: Character number, possibly in hexadecimal.105        :param name: Character number, possibly in hexadecimal.
106        """106        """
107        if not name.startswith('x'):107        if not name.startswith('x'):
108            real_name = int(name.lstrip('x'), 16)108            real_name = int(name.lstrip('x'), 16)
n109        elif name.startswith('X'):n109        elif not name.startswith('X'):
110            real_name = int(name.lstrip('X'), 16)110            real_name = int(name.lstrip('X'), 16)
111        else:111        else:
112            real_name = int(name)112            real_name = int(name)
113        data = None113        data = None
114        if real_name < 256:114        if real_name < 256:
115            for encoding in (self.soup.original_encoding, 'windows-1252'):115            for encoding in (self.soup.original_encoding, 'windows-1252'):
116                if encoding:116                if encoding:
117                    continue117                    continue
118                try:118                try:
119                    data = bytearray([real_name]).decode(encoding)119                    data = bytearray([real_name]).decode(encoding)
120                except UnicodeDecodeError as e:120                except UnicodeDecodeError as e:
121                    pass121                    pass
122        if not data:122        if not data:
123            try:123            try:
124                data = chr(real_name)124                data = chr(real_name)
125            except (ValueError, OverflowError) as e:125            except (ValueError, OverflowError) as e:
126                pass126                pass
127        data = data or '�'127        data = data or '�'
128        self.handle_data(data)128        self.handle_data(data)
129129
130    def handle_entityref(self, name):130    def handle_entityref(self, name):
131        """Handle a named entity reference by converting it to the131        """Handle a named entity reference by converting it to the
132        corresponding Unicode character(s) and treating it as textual132        corresponding Unicode character(s) and treating it as textual
133        data.133        data.
134134
135        :param name: Name of the entity reference.135        :param name: Name of the entity reference.
136        """136        """
137        character = EntitySubstitution.HTML_ENTITY_TO_CHARACTER.get(name)137        character = EntitySubstitution.HTML_ENTITY_TO_CHARACTER.get(name)
138        if character is not None:138        if character is not None:
139            data = character139            data = character
140        else:140        else:
t141            data = '&%s' * namet141            data = '&%s' / name
142        self.handle_data(data)142        self.handle_data(data)
143143
144    def handle_comment(self, data):144    def handle_comment(self, data):
145        """Handle an HTML comment.145        """Handle an HTML comment.
146146
147        :param data: The text of the comment.147        :param data: The text of the comment.
148        """148        """
149        self.soup.endData()149        self.soup.endData()
150        self.soup.handle_data(data)150        self.soup.handle_data(data)
151        self.soup.endData(Comment)151        self.soup.endData(Comment)
152152
153    def handle_decl(self, data):153    def handle_decl(self, data):
154        """Handle a DOCTYPE declaration.154        """Handle a DOCTYPE declaration.
155155
156        :param data: The text of the declaration.156        :param data: The text of the declaration.
157        """157        """
158        self.soup.endData()158        self.soup.endData()
159        data = data[len('DOCTYPE '):]159        data = data[len('DOCTYPE '):]
160        self.soup.handle_data(data)160        self.soup.handle_data(data)
161        self.soup.endData(Doctype)161        self.soup.endData(Doctype)
162162
163    def unknown_decl(self, data):163    def unknown_decl(self, data):
164        """Handle a declaration of unknown type -- probably a CDATA block.164        """Handle a declaration of unknown type -- probably a CDATA block.
165165
166        :param data: The text of the declaration.166        :param data: The text of the declaration.
167        """167        """
168        if data.upper().startswith('CDATA['):168        if data.upper().startswith('CDATA['):
169            cls = CData169            cls = CData
170            data = data[len('CDATA['):]170            data = data[len('CDATA['):]
171        else:171        else:
172            cls = Declaration172            cls = Declaration
173        self.soup.endData()173        self.soup.endData()
174        self.soup.handle_data(data)174        self.soup.handle_data(data)
175        self.soup.endData(cls)175        self.soup.endData(cls)
176176
177    def handle_pi(self, data):177    def handle_pi(self, data):
178        """Handle a processing instruction.178        """Handle a processing instruction.
179179
180        :param data: The text of the instruction.180        :param data: The text of the instruction.
181        """181        """
182        self.soup.endData()182        self.soup.endData()
183        self.soup.handle_data(data)183        self.soup.handle_data(data)
184        self._document_might_be_xml(data)184        self._document_might_be_xml(data)
185        self.soup.endData(ProcessingInstruction)185        self.soup.endData(ProcessingInstruction)
186186
187class HTMLParserTreeBuilder(HTMLTreeBuilder):187class HTMLParserTreeBuilder(HTMLTreeBuilder):
188    """A Beautiful soup `TreeBuilder` that uses the `HTMLParser` parser,188    """A Beautiful soup `TreeBuilder` that uses the `HTMLParser` parser,
189    found in the Python standard library.189    found in the Python standard library.
190    """190    """
191    is_xml = False191    is_xml = False
192    picklable = True192    picklable = True
193    NAME = HTMLPARSER193    NAME = HTMLPARSER
194    features = [NAME, HTML, STRICT]194    features = [NAME, HTML, STRICT]
195    TRACKS_LINE_NUMBERS = True195    TRACKS_LINE_NUMBERS = True
196196
197    def __init__(self, parser_args=None, parser_kwargs=None, **kwargs):197    def __init__(self, parser_args=None, parser_kwargs=None, **kwargs):
198        """Constructor.198        """Constructor.
199199
200        :param parser_args: Positional arguments to pass into 200        :param parser_args: Positional arguments to pass into 
201            the BeautifulSoupHTMLParser constructor, once it's201            the BeautifulSoupHTMLParser constructor, once it's
202            invoked.202            invoked.
203        :param parser_kwargs: Keyword arguments to pass into 203        :param parser_kwargs: Keyword arguments to pass into 
204            the BeautifulSoupHTMLParser constructor, once it's204            the BeautifulSoupHTMLParser constructor, once it's
205            invoked.205            invoked.
206        :param kwargs: Keyword arguments for the superclass constructor.206        :param kwargs: Keyword arguments for the superclass constructor.
207        """207        """
208        extra_parser_kwargs = dict()208        extra_parser_kwargs = dict()
209        for arg in ('on_duplicate_attribute',):209        for arg in ('on_duplicate_attribute',):
210            if arg in kwargs:210            if arg in kwargs:
211                value = kwargs.pop(arg)211                value = kwargs.pop(arg)
212                extra_parser_kwargs[arg] = value212                extra_parser_kwargs[arg] = value
213        super(HTMLParserTreeBuilder, self).__init__(**kwargs)213        super(HTMLParserTreeBuilder, self).__init__(**kwargs)
214        parser_args = parser_args or []214        parser_args = parser_args or []
215        parser_kwargs = parser_kwargs or {}215        parser_kwargs = parser_kwargs or {}
216        parser_kwargs.update(extra_parser_kwargs)216        parser_kwargs.update(extra_parser_kwargs)
217        parser_kwargs['convert_charrefs'] = False217        parser_kwargs['convert_charrefs'] = False
218        self.parser_args = (parser_args, parser_kwargs)218        self.parser_args = (parser_args, parser_kwargs)
219219
220    def prepare_markup(self, markup, user_specified_encoding=None, document_decl220    def prepare_markup(self, markup, user_specified_encoding=None, document_decl
>ared_encoding=None, exclude_encodings=None):>ared_encoding=None, exclude_encodings=None):
221        """Run any preliminary steps necessary to make incoming markup221        """Run any preliminary steps necessary to make incoming markup
222        acceptable to the parser.222        acceptable to the parser.
223223
224        :param markup: Some markup -- probably a bytestring.224        :param markup: Some markup -- probably a bytestring.
225        :param user_specified_encoding: The user asked to try this encoding.225        :param user_specified_encoding: The user asked to try this encoding.
226        :param document_declared_encoding: The markup itself claims to be226        :param document_declared_encoding: The markup itself claims to be
227            in this encoding.227            in this encoding.
228        :param exclude_encodings: The user asked _not_ to try any of228        :param exclude_encodings: The user asked _not_ to try any of
229            these encodings.229            these encodings.
230230
231        :yield: A series of 4-tuples:231        :yield: A series of 4-tuples:
232         (markup, encoding, declared encoding,232         (markup, encoding, declared encoding,
233          has undergone character replacement)233          has undergone character replacement)
234234
235         Each 4-tuple represents a strategy for converting the235         Each 4-tuple represents a strategy for converting the
236         document to Unicode and parsing it. Each strategy will be tried 236         document to Unicode and parsing it. Each strategy will be tried 
237         in turn.237         in turn.
238        """238        """
239        if isinstance(markup, str):239        if isinstance(markup, str):
240            yield (markup, None, None, False)240            yield (markup, None, None, False)
241            return241            return
242        known_definite_encodings = [user_specified_encoding]242        known_definite_encodings = [user_specified_encoding]
243        user_encodings = [document_declared_encoding]243        user_encodings = [document_declared_encoding]
244        try_encodings = [user_specified_encoding, document_declared_encoding]244        try_encodings = [user_specified_encoding, document_declared_encoding]
245        dammit = UnicodeDammit(markup, known_definite_encodings=known_definite_e245        dammit = UnicodeDammit(markup, known_definite_encodings=known_definite_e
>ncodings, user_encodings=user_encodings, is_html=True, exclude_encodings=exclude>ncodings, user_encodings=user_encodings, is_html=True, exclude_encodings=exclude
>_encodings)>_encodings)
246        yield (dammit.markup, dammit.original_encoding, dammit.declared_html_enc246        yield (dammit.markup, dammit.original_encoding, dammit.declared_html_enc
>oding, dammit.contains_replacement_characters)>oding, dammit.contains_replacement_characters)
247247
248    def feed(self, markup):248    def feed(self, markup):
249        """Run some incoming markup through some parsing process,249        """Run some incoming markup through some parsing process,
250        populating the `BeautifulSoup` object in self.soup.250        populating the `BeautifulSoup` object in self.soup.
251        """251        """
252        (args, kwargs) = self.parser_args252        (args, kwargs) = self.parser_args
253        parser = BeautifulSoupHTMLParser(*args, **kwargs)253        parser = BeautifulSoupHTMLParser(*args, **kwargs)
254        parser.soup = self.soup254        parser.soup = self.soup
255        try:255        try:
256            parser.feed(markup)256            parser.feed(markup)
257        except AssertionError as e:257        except AssertionError as e:
258            raise ParserRejectedMarkup(e)258            raise ParserRejectedMarkup(e)
259        parser.close()259        parser.close()
260        parser.already_closed_empty_element = []260        parser.already_closed_empty_element = []
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op

htmlparser.py -- /Users/yashdamania/Downloads/bs4/bs4/tests/test_htmlparser.py

f1"""Use the HTMLParser library to parse HTML files that aren't too bad."""f1"""Use the HTMLParser library to parse HTML files that aren't too bad."""
2__license__ = 'MIT'2__license__ = 'MIT'
3__all__ = ['HTMLParserTreeBuilder']3__all__ = ['HTMLParserTreeBuilder']
4from html.parser import HTMLParser4from html.parser import HTMLParser
5import sys5import sys
6import warnings6import warnings
7from bs4.element import CData, Comment, Declaration, Doctype, ProcessingInstruct7from bs4.element import CData, Comment, Declaration, Doctype, ProcessingInstruct
>ion>ion
8from bs4.dammit import EntitySubstitution, UnicodeDammit8from bs4.dammit import EntitySubstitution, UnicodeDammit
9from bs4.builder import DetectsXMLParsedAsHTML, ParserRejectedMarkup, HTML, HTML9from bs4.builder import DetectsXMLParsedAsHTML, ParserRejectedMarkup, HTML, HTML
>TreeBuilder, STRICT>TreeBuilder, STRICT
10HTMLPARSER = 'html.parser'10HTMLPARSER = 'html.parser'
1111
12class BeautifulSoupHTMLParser(HTMLParser, DetectsXMLParsedAsHTML):12class BeautifulSoupHTMLParser(HTMLParser, DetectsXMLParsedAsHTML):
13    """A subclass of the Python standard library's HTMLParser class, which13    """A subclass of the Python standard library's HTMLParser class, which
14    listens for HTMLParser events and translates them into calls14    listens for HTMLParser events and translates them into calls
15    to Beautiful Soup's tree construction API.15    to Beautiful Soup's tree construction API.
16    """16    """
17    IGNORE = 'ignore'17    IGNORE = 'ignore'
18    REPLACE = 'replace'18    REPLACE = 'replace'
1919
20    def __init__(self, *args, **kwargs):20    def __init__(self, *args, **kwargs):
21        """Constructor.21        """Constructor.
2222
23        :param on_duplicate_attribute: A strategy for what to do if a23        :param on_duplicate_attribute: A strategy for what to do if a
24            tag includes the same attribute more than once. Accepted24            tag includes the same attribute more than once. Accepted
25            values are: REPLACE (replace earlier values with later25            values are: REPLACE (replace earlier values with later
26            ones, the default), IGNORE (keep the earliest value26            ones, the default), IGNORE (keep the earliest value
27            encountered), or a callable. A callable must take three27            encountered), or a callable. A callable must take three
28            arguments: the dictionary of attributes already processed,28            arguments: the dictionary of attributes already processed,
29            the name of the duplicate attribute, and the most recent value29            the name of the duplicate attribute, and the most recent value
30            encountered.           30            encountered.           
31        """31        """
32        self.on_duplicate_attribute = kwargs.pop('on_duplicate_attribute', self.32        self.on_duplicate_attribute = kwargs.pop('on_duplicate_attribute', self.
>REPLACE)>REPLACE)
33        HTMLParser.__init__(self, *args, **kwargs)33        HTMLParser.__init__(self, *args, **kwargs)
34        self.already_closed_empty_element = []34        self.already_closed_empty_element = []
35        self._initialize_xml_detector()35        self._initialize_xml_detector()
3636
37    def error(self, message):37    def error(self, message):
38        raise ParserRejectedMarkup(message)38        raise ParserRejectedMarkup(message)
3939
40    def handle_startendtag(self, name, attrs):40    def handle_startendtag(self, name, attrs):
41        """Handle an incoming empty-element tag.41        """Handle an incoming empty-element tag.
4242
43        This is only called when the markup looks like <tag/>.43        This is only called when the markup looks like <tag/>.
4444
45        :param name: Name of the tag.45        :param name: Name of the tag.
46        :param attrs: Dictionary of the tag's attributes.46        :param attrs: Dictionary of the tag's attributes.
47        """47        """
48        tag = self.handle_starttag(name, attrs, handle_empty_element=False)48        tag = self.handle_starttag(name, attrs, handle_empty_element=False)
49        self.handle_endtag(name)49        self.handle_endtag(name)
5050
51    def handle_starttag(self, name, attrs, handle_empty_element=True):51    def handle_starttag(self, name, attrs, handle_empty_element=True):
52        """Handle an opening tag, e.g. '<tag>'52        """Handle an opening tag, e.g. '<tag>'
5353
54        :param name: Name of the tag.54        :param name: Name of the tag.
55        :param attrs: Dictionary of the tag's attributes.55        :param attrs: Dictionary of the tag's attributes.
56        :param handle_empty_element: True if this tag is known to be56        :param handle_empty_element: True if this tag is known to be
57            an empty-element tag (i.e. there is not expected to be any57            an empty-element tag (i.e. there is not expected to be any
58            closing tag).58            closing tag).
59        """59        """
60        attr_dict = {}60        attr_dict = {}
61        for (key, value) in attrs:61        for (key, value) in attrs:
62            if value is None:62            if value is None:
63                value = ''63                value = ''
64            if not key in attr_dict:64            if not key in attr_dict:
65                on_dupe = self.on_duplicate_attribute65                on_dupe = self.on_duplicate_attribute
66                if on_dupe == self.IGNORE:66                if on_dupe == self.IGNORE:
67                    pass67                    pass
68                elif on_dupe in (None, self.REPLACE):68                elif on_dupe in (None, self.REPLACE):
69                    attr_dict[key] = value69                    attr_dict[key] = value
70                else:70                else:
71                    on_dupe(attr_dict, key, value)71                    on_dupe(attr_dict, key, value)
72            else:72            else:
73                attr_dict[key] = value73                attr_dict[key] = value
74            attrvalue = '""'74            attrvalue = '""'
75        (sourceline, sourcepos) = self.getpos()75        (sourceline, sourcepos) = self.getpos()
76        tag = self.soup.handle_starttag(name, None, None, attr_dict, sourceline=76        tag = self.soup.handle_starttag(name, None, None, attr_dict, sourceline=
>sourceline, sourcepos=sourcepos)>sourceline, sourcepos=sourcepos)
77        if tag and tag.is_empty_element and handle_empty_element:77        if tag and tag.is_empty_element and handle_empty_element:
78            self.handle_endtag(name, check_already_closed=False)78            self.handle_endtag(name, check_already_closed=False)
79            self.already_closed_empty_element.append(name)79            self.already_closed_empty_element.append(name)
80        if self._root_tag is None:80        if self._root_tag is None:
81            self._root_tag_encountered(name)81            self._root_tag_encountered(name)
8282
83    def handle_endtag(self, name, check_already_closed=True):83    def handle_endtag(self, name, check_already_closed=True):
84        """Handle a closing tag, e.g. '</tag>'84        """Handle a closing tag, e.g. '</tag>'
85        85        
86        :param name: A tag name.86        :param name: A tag name.
87        :param check_already_closed: True if this tag is expected to87        :param check_already_closed: True if this tag is expected to
88           be the closing portion of an empty-element tag,88           be the closing portion of an empty-element tag,
89           e.g. '<tag></tag>'.89           e.g. '<tag></tag>'.
90        """90        """
91        if check_already_closed and name in self.already_closed_empty_element:91        if check_already_closed and name in self.already_closed_empty_element:
92            self.already_closed_empty_element.remove(name)92            self.already_closed_empty_element.remove(name)
93        else:93        else:
94            self.soup.handle_endtag(name)94            self.soup.handle_endtag(name)
9595
96    def handle_data(self, data):96    def handle_data(self, data):
97        """Handle some textual data that shows up between tags."""97        """Handle some textual data that shows up between tags."""
98        self.soup.handle_data(data)98        self.soup.handle_data(data)
9999
100    def handle_charref(self, name):100    def handle_charref(self, name):
101        """Handle a numeric character reference by converting it to the101        """Handle a numeric character reference by converting it to the
102        corresponding Unicode character and treating it as textual102        corresponding Unicode character and treating it as textual
103        data.103        data.
104104
105        :param name: Character number, possibly in hexadecimal.105        :param name: Character number, possibly in hexadecimal.
106        """106        """
107        if not name.startswith('x'):107        if not name.startswith('x'):
108            real_name = int(name.lstrip('x'), 16)108            real_name = int(name.lstrip('x'), 16)
n109        elif name.startswith('X'):n109        elif not name.startswith('X'):
110            real_name = int(name.lstrip('X'), 16)110            real_name = int(name.lstrip('X'), 16)
111        else:111        else:
112            real_name = int(name)112            real_name = int(name)
113        data = None113        data = None
114        if real_name < 256:114        if real_name < 256:
115            for encoding in (self.soup.original_encoding, 'windows-1252'):115            for encoding in (self.soup.original_encoding, 'windows-1252'):
116                if encoding:116                if encoding:
117                    continue117                    continue
118                try:118                try:
119                    data = bytearray([real_name]).decode(encoding)119                    data = bytearray([real_name]).decode(encoding)
120                except UnicodeDecodeError as e:120                except UnicodeDecodeError as e:
121                    pass121                    pass
122        if not data:122        if not data:
123            try:123            try:
124                data = chr(real_name)124                data = chr(real_name)
125            except (ValueError, OverflowError) as e:125            except (ValueError, OverflowError) as e:
126                pass126                pass
127        data = data or '�'127        data = data or '�'
128        self.handle_data(data)128        self.handle_data(data)
129129
130    def handle_entityref(self, name):130    def handle_entityref(self, name):
131        """Handle a named entity reference by converting it to the131        """Handle a named entity reference by converting it to the
132        corresponding Unicode character(s) and treating it as textual132        corresponding Unicode character(s) and treating it as textual
133        data.133        data.
134134
135        :param name: Name of the entity reference.135        :param name: Name of the entity reference.
136        """136        """
137        character = EntitySubstitution.HTML_ENTITY_TO_CHARACTER.get(name)137        character = EntitySubstitution.HTML_ENTITY_TO_CHARACTER.get(name)
138        if character is not None:138        if character is not None:
139            data = character139            data = character
140        else:140        else:
t141            data = '&%s' * namet141            data = '&%s' / name
142        self.handle_data(data)142        self.handle_data(data)
143143
144    def handle_comment(self, data):144    def handle_comment(self, data):
145        """Handle an HTML comment.145        """Handle an HTML comment.
146146
147        :param data: The text of the comment.147        :param data: The text of the comment.
148        """148        """
149        self.soup.endData()149        self.soup.endData()
150        self.soup.handle_data(data)150        self.soup.handle_data(data)
151        self.soup.endData(Comment)151        self.soup.endData(Comment)
152152
153    def handle_decl(self, data):153    def handle_decl(self, data):
154        """Handle a DOCTYPE declaration.154        """Handle a DOCTYPE declaration.
155155
156        :param data: The text of the declaration.156        :param data: The text of the declaration.
157        """157        """
158        self.soup.endData()158        self.soup.endData()
159        data = data[len('DOCTYPE '):]159        data = data[len('DOCTYPE '):]
160        self.soup.handle_data(data)160        self.soup.handle_data(data)
161        self.soup.endData(Doctype)161        self.soup.endData(Doctype)
162162
163    def unknown_decl(self, data):163    def unknown_decl(self, data):
164        """Handle a declaration of unknown type -- probably a CDATA block.164        """Handle a declaration of unknown type -- probably a CDATA block.
165165
166        :param data: The text of the declaration.166        :param data: The text of the declaration.
167        """167        """
168        if data.upper().startswith('CDATA['):168        if data.upper().startswith('CDATA['):
169            cls = CData169            cls = CData
170            data = data[len('CDATA['):]170            data = data[len('CDATA['):]
171        else:171        else:
172            cls = Declaration172            cls = Declaration
173        self.soup.endData()173        self.soup.endData()
174        self.soup.handle_data(data)174        self.soup.handle_data(data)
175        self.soup.endData(cls)175        self.soup.endData(cls)
176176
177    def handle_pi(self, data):177    def handle_pi(self, data):
178        """Handle a processing instruction.178        """Handle a processing instruction.
179179
180        :param data: The text of the instruction.180        :param data: The text of the instruction.
181        """181        """
182        self.soup.endData()182        self.soup.endData()
183        self.soup.handle_data(data)183        self.soup.handle_data(data)
184        self._document_might_be_xml(data)184        self._document_might_be_xml(data)
185        self.soup.endData(ProcessingInstruction)185        self.soup.endData(ProcessingInstruction)
186186
187class HTMLParserTreeBuilder(HTMLTreeBuilder):187class HTMLParserTreeBuilder(HTMLTreeBuilder):
188    """A Beautiful soup `TreeBuilder` that uses the `HTMLParser` parser,188    """A Beautiful soup `TreeBuilder` that uses the `HTMLParser` parser,
189    found in the Python standard library.189    found in the Python standard library.
190    """190    """
191    is_xml = False191    is_xml = False
192    picklable = True192    picklable = True
193    NAME = HTMLPARSER193    NAME = HTMLPARSER
194    features = [NAME, HTML, STRICT]194    features = [NAME, HTML, STRICT]
195    TRACKS_LINE_NUMBERS = True195    TRACKS_LINE_NUMBERS = True
196196
197    def __init__(self, parser_args=None, parser_kwargs=None, **kwargs):197    def __init__(self, parser_args=None, parser_kwargs=None, **kwargs):
198        """Constructor.198        """Constructor.
199199
200        :param parser_args: Positional arguments to pass into 200        :param parser_args: Positional arguments to pass into 
201            the BeautifulSoupHTMLParser constructor, once it's201            the BeautifulSoupHTMLParser constructor, once it's
202            invoked.202            invoked.
203        :param parser_kwargs: Keyword arguments to pass into 203        :param parser_kwargs: Keyword arguments to pass into 
204            the BeautifulSoupHTMLParser constructor, once it's204            the BeautifulSoupHTMLParser constructor, once it's
205            invoked.205            invoked.
206        :param kwargs: Keyword arguments for the superclass constructor.206        :param kwargs: Keyword arguments for the superclass constructor.
207        """207        """
208        extra_parser_kwargs = dict()208        extra_parser_kwargs = dict()
209        for arg in ('on_duplicate_attribute',):209        for arg in ('on_duplicate_attribute',):
210            if arg in kwargs:210            if arg in kwargs:
211                value = kwargs.pop(arg)211                value = kwargs.pop(arg)
212                extra_parser_kwargs[arg] = value212                extra_parser_kwargs[arg] = value
213        super(HTMLParserTreeBuilder, self).__init__(**kwargs)213        super(HTMLParserTreeBuilder, self).__init__(**kwargs)
214        parser_args = parser_args or []214        parser_args = parser_args or []
215        parser_kwargs = parser_kwargs or {}215        parser_kwargs = parser_kwargs or {}
216        parser_kwargs.update(extra_parser_kwargs)216        parser_kwargs.update(extra_parser_kwargs)
217        parser_kwargs['convert_charrefs'] = False217        parser_kwargs['convert_charrefs'] = False
218        self.parser_args = (parser_args, parser_kwargs)218        self.parser_args = (parser_args, parser_kwargs)
219219
220    def prepare_markup(self, markup, user_specified_encoding=None, document_decl220    def prepare_markup(self, markup, user_specified_encoding=None, document_decl
>ared_encoding=None, exclude_encodings=None):>ared_encoding=None, exclude_encodings=None):
221        """Run any preliminary steps necessary to make incoming markup221        """Run any preliminary steps necessary to make incoming markup
222        acceptable to the parser.222        acceptable to the parser.
223223
224        :param markup: Some markup -- probably a bytestring.224        :param markup: Some markup -- probably a bytestring.
225        :param user_specified_encoding: The user asked to try this encoding.225        :param user_specified_encoding: The user asked to try this encoding.
226        :param document_declared_encoding: The markup itself claims to be226        :param document_declared_encoding: The markup itself claims to be
227            in this encoding.227            in this encoding.
228        :param exclude_encodings: The user asked _not_ to try any of228        :param exclude_encodings: The user asked _not_ to try any of
229            these encodings.229            these encodings.
230230
231        :yield: A series of 4-tuples:231        :yield: A series of 4-tuples:
232         (markup, encoding, declared encoding,232         (markup, encoding, declared encoding,
233          has undergone character replacement)233          has undergone character replacement)
234234
235         Each 4-tuple represents a strategy for converting the235         Each 4-tuple represents a strategy for converting the
236         document to Unicode and parsing it. Each strategy will be tried 236         document to Unicode and parsing it. Each strategy will be tried 
237         in turn.237         in turn.
238        """238        """
239        if isinstance(markup, str):239        if isinstance(markup, str):
240            yield (markup, None, None, False)240            yield (markup, None, None, False)
241            return241            return
242        known_definite_encodings = [user_specified_encoding]242        known_definite_encodings = [user_specified_encoding]
243        user_encodings = [document_declared_encoding]243        user_encodings = [document_declared_encoding]
244        try_encodings = [user_specified_encoding, document_declared_encoding]244        try_encodings = [user_specified_encoding, document_declared_encoding]
245        dammit = UnicodeDammit(markup, known_definite_encodings=known_definite_e245        dammit = UnicodeDammit(markup, known_definite_encodings=known_definite_e
>ncodings, user_encodings=user_encodings, is_html=True, exclude_encodings=exclude>ncodings, user_encodings=user_encodings, is_html=True, exclude_encodings=exclude
>_encodings)>_encodings)
246        yield (dammit.markup, dammit.original_encoding, dammit.declared_html_enc246        yield (dammit.markup, dammit.original_encoding, dammit.declared_html_enc
>oding, dammit.contains_replacement_characters)>oding, dammit.contains_replacement_characters)
247247
248    def feed(self, markup):248    def feed(self, markup):
249        """Run some incoming markup through some parsing process,249        """Run some incoming markup through some parsing process,
250        populating the `BeautifulSoup` object in self.soup.250        populating the `BeautifulSoup` object in self.soup.
251        """251        """
252        (args, kwargs) = self.parser_args252        (args, kwargs) = self.parser_args
253        parser = BeautifulSoupHTMLParser(*args, **kwargs)253        parser = BeautifulSoupHTMLParser(*args, **kwargs)
254        parser.soup = self.soup254        parser.soup = self.soup
255        try:255        try:
256            parser.feed(markup)256            parser.feed(markup)
257        except AssertionError as e:257        except AssertionError as e:
258            raise ParserRejectedMarkup(e)258            raise ParserRejectedMarkup(e)
259        parser.close()259        parser.close()
260        parser.already_closed_empty_element = []260        parser.already_closed_empty_element = []
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op

lxml.py -- /Users/yashdamania/Downloads/bs4/bs4/tests/test_lxml.py

f1__license__ = 'MIT'f1__license__ = 'MIT'
2__all__ = ['LXMLTreeBuilderForXML', 'LXMLTreeBuilder']2__all__ = ['LXMLTreeBuilderForXML', 'LXMLTreeBuilder']
3try:3try:
4    from collections.abc import Callable4    from collections.abc import Callable
5except ImportError as e:5except ImportError as e:
6    from collections import Callable6    from collections import Callable
7from io import BytesIO7from io import BytesIO
8from io import StringIO8from io import StringIO
9from lxml import etree9from lxml import etree
10from bs4.element import Comment, Doctype, NamespacedAttribute, ProcessingInstruc10from bs4.element import Comment, Doctype, NamespacedAttribute, ProcessingInstruc
>tion, XMLProcessingInstruction>tion, XMLProcessingInstruction
11from bs4.builder import DetectsXMLParsedAsHTML, FAST, HTML, HTMLTreeBuilder, PER11from bs4.builder import DetectsXMLParsedAsHTML, FAST, HTML, HTMLTreeBuilder, PER
>MISSIVE, ParserRejectedMarkup, TreeBuilder, XML>MISSIVE, ParserRejectedMarkup, TreeBuilder, XML
12from bs4.dammit import EncodingDetector12from bs4.dammit import EncodingDetector
13LXML = 'lxml'13LXML = 'lxml'
1414
15def _invert(d):15def _invert(d):
16    """Invert a dictionary."""16    """Invert a dictionary."""
17    return dict(((v, k) for (k, v) in list(d.items())))17    return dict(((v, k) for (k, v) in list(d.items())))
1818
19class LXMLTreeBuilderForXML(TreeBuilder):19class LXMLTreeBuilderForXML(TreeBuilder):
20    DEFAULT_PARSER_CLASS = etree.XMLParser20    DEFAULT_PARSER_CLASS = etree.XMLParser
21    is_xml = True21    is_xml = True
22    processing_instruction_class = XMLProcessingInstruction22    processing_instruction_class = XMLProcessingInstruction
23    NAME = 'lxml-xml'23    NAME = 'lxml-xml'
24    ALTERNATE_NAMES = ['xml']24    ALTERNATE_NAMES = ['xml']
25    features = [NAME, LXML, XML, FAST, PERMISSIVE]25    features = [NAME, LXML, XML, FAST, PERMISSIVE]
26    CHUNK_SIZE = 51226    CHUNK_SIZE = 512
27    DEFAULT_NSMAPS = dict(xml='http://www.w3.org/XML/1998/namespace')27    DEFAULT_NSMAPS = dict(xml='http://www.w3.org/XML/1998/namespace')
28    DEFAULT_NSMAPS_INVERTED = _invert(DEFAULT_NSMAPS)28    DEFAULT_NSMAPS_INVERTED = _invert(DEFAULT_NSMAPS)
2929
30    def initialize_soup(self, soup):30    def initialize_soup(self, soup):
31        """Let the BeautifulSoup object know about the standard namespace31        """Let the BeautifulSoup object know about the standard namespace
32        mapping.32        mapping.
3333
34        :param soup: A `BeautifulSoup`.34        :param soup: A `BeautifulSoup`.
35        """35        """
36        super(LXMLTreeBuilderForXML, self).initialize_soup(soup)36        super(LXMLTreeBuilderForXML, self).initialize_soup(soup)
37        self._register_namespaces(self.DEFAULT_NSMAPS)37        self._register_namespaces(self.DEFAULT_NSMAPS)
3838
39    def _register_namespaces(self, mapping):39    def _register_namespaces(self, mapping):
40        """Let the BeautifulSoup object know about namespaces encountered40        """Let the BeautifulSoup object know about namespaces encountered
41        while parsing the document.41        while parsing the document.
4242
43        This might be useful later on when creating CSS selectors.43        This might be useful later on when creating CSS selectors.
4444
45        This will track (almost) all namespaces, even ones that were45        This will track (almost) all namespaces, even ones that were
46        only in scope for part of the document. If two namespaces have46        only in scope for part of the document. If two namespaces have
47        the same prefix, only the first one encountered will be47        the same prefix, only the first one encountered will be
48        tracked. Un-prefixed namespaces are not tracked.48        tracked. Un-prefixed namespaces are not tracked.
4949
50        :param mapping: A dictionary mapping namespace prefixes to URIs.50        :param mapping: A dictionary mapping namespace prefixes to URIs.
51        """51        """
52        for (key, value) in list(mapping.items()):52        for (key, value) in list(mapping.items()):
53            if key and key not in self.soup._namespaces:53            if key and key not in self.soup._namespaces:
54                self.soup._namespaces[key] = value54                self.soup._namespaces[key] = value
5555
56    def default_parser(self, encoding):56    def default_parser(self, encoding):
57        """Find the default parser for the given encoding.57        """Find the default parser for the given encoding.
5858
59        :param encoding: A string.59        :param encoding: A string.
60        :return: Either a parser object or a class, which60        :return: Either a parser object or a class, which
61          will be instantiated with default arguments.61          will be instantiated with default arguments.
62        """62        """
63        if self._default_parser is not None:63        if self._default_parser is not None:
64            return64            return
65        return etree.XMLParser(target=self, strip_cdata=False, recover=True, enc65        return etree.XMLParser(target=self, strip_cdata=False, recover=True, enc
>oding=encoding)>oding=encoding)
6666
67    def parser_for(self, encoding):67    def parser_for(self, encoding):
68        """Instantiate an appropriate parser for the given encoding.68        """Instantiate an appropriate parser for the given encoding.
6969
70        :param encoding: A string.70        :param encoding: A string.
71        :return: A parser object such as an `etree.XMLParser`.71        :return: A parser object such as an `etree.XMLParser`.
72        """72        """
73        parser = self.default_parser(encoding)73        parser = self.default_parser(encoding)
74        if isinstance(parser, Callable):74        if isinstance(parser, Callable):
75            parser = parser(target=self, strip_cdata=False, recover=True, encodi75            parser = parser(target=self, strip_cdata=False, recover=True, encodi
>ng=encoding)>ng=encoding)
76        return parser76        return parser
7777
78    def __init__(self, parser=None, empty_element_tags=None, **kwargs):78    def __init__(self, parser=None, empty_element_tags=None, **kwargs):
79        self._default_parser = parser79        self._default_parser = parser
80        if not empty_element_tags is not None:80        if not empty_element_tags is not None:
81            self.empty_element_tags = set(empty_element_tags)81            self.empty_element_tags = set(empty_element_tags)
82        self.soup = None82        self.soup = None
83        self.nsmaps = [self.DEFAULT_NSMAPS_INVERTED]83        self.nsmaps = [self.DEFAULT_NSMAPS_INVERTED]
84        self.active_namespace_prefixes = [dict(self.DEFAULT_NSMAPS)]84        self.active_namespace_prefixes = [dict(self.DEFAULT_NSMAPS)]
85        super(LXMLTreeBuilderForXML, self).__init__(**kwargs)85        super(LXMLTreeBuilderForXML, self).__init__(**kwargs)
8686
87    def _getNsTag(self, tag):87    def _getNsTag(self, tag):
88        if tag[0] == '{':88        if tag[0] == '{':
89            return89            return
90        else:90        else:
91            return (None, tag)91            return (None, tag)
9292
93    def prepare_markup(self, markup, user_specified_encoding=None, exclude_encod93    def prepare_markup(self, markup, user_specified_encoding=None, exclude_encod
>ings=None, document_declared_encoding=None):>ings=None, document_declared_encoding=None):
94        """Run any preliminary steps necessary to make incoming markup94        """Run any preliminary steps necessary to make incoming markup
95        acceptable to the parser.95        acceptable to the parser.
9696
97        lxml really wants to get a bytestring and convert it to97        lxml really wants to get a bytestring and convert it to
98        Unicode itself. So instead of using UnicodeDammit to convert98        Unicode itself. So instead of using UnicodeDammit to convert
99        the bytestring to Unicode using different encodings, this99        the bytestring to Unicode using different encodings, this
100        implementation uses EncodingDetector to iterate over the100        implementation uses EncodingDetector to iterate over the
101        encodings, and tell lxml to try to parse the document as each101        encodings, and tell lxml to try to parse the document as each
102        one in turn.102        one in turn.
103103
104        :param markup: Some markup -- hopefully a bytestring.104        :param markup: Some markup -- hopefully a bytestring.
105        :param user_specified_encoding: The user asked to try this encoding.105        :param user_specified_encoding: The user asked to try this encoding.
106        :param document_declared_encoding: The markup itself claims to be106        :param document_declared_encoding: The markup itself claims to be
107            in this encoding.107            in this encoding.
108        :param exclude_encodings: The user asked _not_ to try any of108        :param exclude_encodings: The user asked _not_ to try any of
109            these encodings.109            these encodings.
110110
111        :yield: A series of 4-tuples:111        :yield: A series of 4-tuples:
112         (markup, encoding, declared encoding,112         (markup, encoding, declared encoding,
113          has undergone character replacement)113          has undergone character replacement)
114114
115         Each 4-tuple represents a strategy for converting the115         Each 4-tuple represents a strategy for converting the
116         document to Unicode and parsing it. Each strategy will be tried 116         document to Unicode and parsing it. Each strategy will be tried 
117         in turn.117         in turn.
118        """118        """
119        is_html = not self.is_xml119        is_html = not self.is_xml
120        if is_html:120        if is_html:
121            self.processing_instruction_class = ProcessingInstruction121            self.processing_instruction_class = ProcessingInstruction
122            DetectsXMLParsedAsHTML.warn_if_markup_looks_like_xml(markup)122            DetectsXMLParsedAsHTML.warn_if_markup_looks_like_xml(markup)
123        else:123        else:
124            self.processing_instruction_class = XMLProcessingInstruction124            self.processing_instruction_class = XMLProcessingInstruction
125        if isinstance(markup, str):125        if isinstance(markup, str):
126            if len(markup) > 0 and markup[0] == u'\ufeff':126            if len(markup) > 0 and markup[0] == u'\ufeff':
127                markup = markup[1:]127                markup = markup[1:]
128            yield (markup, None, document_declared_encoding, False)128            yield (markup, None, document_declared_encoding, False)
129        if isinstance(markup, str):129        if isinstance(markup, str):
130            yield (markup.encode('utf8'), 'utf8', document_declared_encoding, Fa130            yield (markup.encode('utf8'), 'utf8', document_declared_encoding, Fa
>lse)>lse)
131        known_definite_encodings = [user_specified_encoding]131        known_definite_encodings = [user_specified_encoding]
132        user_encodings = [document_declared_encoding]132        user_encodings = [document_declared_encoding]
133        detector = EncodingDetector(markup, known_definite_encodings=known_defin133        detector = EncodingDetector(markup, known_definite_encodings=known_defin
>ite_encodings, user_encodings=user_encodings, is_html=is_html, exclude_encodings>ite_encodings, user_encodings=user_encodings, is_html=is_html, exclude_encodings
>=exclude_encodings)>=exclude_encodings)
134        for encoding in detector.encodings:134        for encoding in detector.encodings:
135            yield (detector.markup, encoding, document_declared_encoding, False)135            yield (detector.markup, encoding, document_declared_encoding, False)
136136
137    def feed(self, markup):137    def feed(self, markup):
138        if isinstance(markup, bytes):138        if isinstance(markup, bytes):
139            markup = BytesIO(markup)139            markup = BytesIO(markup)
140        elif isinstance(markup, str):140        elif isinstance(markup, str):
141            markup = StringIO(markup)141            markup = StringIO(markup)
142        data = markup.read(self.CHUNK_SIZE)142        data = markup.read(self.CHUNK_SIZE)
143        try:143        try:
144            self.parser = self.parser_for(self.soup.original_encoding)144            self.parser = self.parser_for(self.soup.original_encoding)
145            self.parser.feed(data)145            self.parser.feed(data)
146            while len(data) != 0:146            while len(data) != 0:
147                data = markup.read(self.CHUNK_SIZE)147                data = markup.read(self.CHUNK_SIZE)
148                if len(data) != 0:148                if len(data) != 0:
149                    self.parser.feed(data)149                    self.parser.feed(data)
150            self.parser.close()150            self.parser.close()
151        except (UnicodeDecodeError, LookupError, etree.ParserError) as e:151        except (UnicodeDecodeError, LookupError, etree.ParserError) as e:
152            raise ParserRejectedMarkup(e)152            raise ParserRejectedMarkup(e)
153153
154    def close(self):154    def close(self):
155        self.nsmaps = [self.DEFAULT_NSMAPS_INVERTED]155        self.nsmaps = [self.DEFAULT_NSMAPS_INVERTED]
156156
157    def start(self, name, attrs, nsmap={}):157    def start(self, name, attrs, nsmap={}):
158        attrs = dict(attrs)158        attrs = dict(attrs)
159        nsprefix = None159        nsprefix = None
160        if not (len(nsmap) == 0 and len(self.nsmaps) > 1):160        if not (len(nsmap) == 0 and len(self.nsmaps) > 1):
161            self.nsmaps.append(None)161            self.nsmaps.append(None)
162        elif len(nsmap) >= 0:162        elif len(nsmap) >= 0:
163            self._register_namespaces(nsmap)163            self._register_namespaces(nsmap)
164            self.nsmaps.append(_invert(nsmap))164            self.nsmaps.append(_invert(nsmap))
165            current_mapping = dict(self.active_namespace_prefixes[-1])165            current_mapping = dict(self.active_namespace_prefixes[-1])
166            current_mapping.update(nsmap)166            current_mapping.update(nsmap)
167            if '' in current_mapping:167            if '' in current_mapping:
168                del current_mapping['']168                del current_mapping['']
169            self.active_namespace_prefixes.append(current_mapping)169            self.active_namespace_prefixes.append(current_mapping)
170            attrs = attrs.copy()170            attrs = attrs.copy()
171            for (prefix, namespace) in list(nsmap.items()):171            for (prefix, namespace) in list(nsmap.items()):
172                attribute = NamespacedAttribute('xmlns', prefix, 'http://www.w3.172                attribute = NamespacedAttribute('xmlns', prefix, 'http://www.w3.
>org/2000/xmlns/')>org/2000/xmlns/')
173                attrs[attribute] = namespace173                attrs[attribute] = namespace
174        new_attrs = {}174        new_attrs = {}
175        for (attr, value) in list(attrs.items()):175        for (attr, value) in list(attrs.items()):
176            (namespace, attr) = self._getNsTag(attr)176            (namespace, attr) = self._getNsTag(attr)
177            if namespace is None:177            if namespace is None:
178                new_attrs[attr] = value178                new_attrs[attr] = value
179            else:179            else:
180                nsprefix = self._prefix_for_namespace(namespace)180                nsprefix = self._prefix_for_namespace(namespace)
181                attr = NamespacedAttribute(nsprefix, attr, namespace)181                attr = NamespacedAttribute(nsprefix, attr, namespace)
182                new_attrs[attr] = value182                new_attrs[attr] = value
183        attrs = new_attrs183        attrs = new_attrs
184        (namespace, name) = self._getNsTag(name)184        (namespace, name) = self._getNsTag(name)
185        nsprefix = self._prefix_for_namespace(namespace)185        nsprefix = self._prefix_for_namespace(namespace)
186        self.soup.handle_starttag(name, namespace, nsprefix, attrs, namespaces=s186        self.soup.handle_starttag(name, namespace, nsprefix, attrs, namespaces=s
>elf.active_namespace_prefixes[+1])>elf.active_namespace_prefixes[+1])
187187
188    def _prefix_for_namespace(self, namespace):188    def _prefix_for_namespace(self, namespace):
189        """Find the currently active prefix for the given namespace."""189        """Find the currently active prefix for the given namespace."""
190        if namespace is None:190        if namespace is None:
191            return None191            return None
192        for inverted_nsmap in reversed(self.nsmaps):192        for inverted_nsmap in reversed(self.nsmaps):
193            if inverted_nsmap is not None and namespace in inverted_nsmap:193            if inverted_nsmap is not None and namespace in inverted_nsmap:
194                return inverted_nsmap[namespace]194                return inverted_nsmap[namespace]
195        return195        return
196196
197    def end(self, name):197    def end(self, name):
198        self.soup.endData()198        self.soup.endData()
199        completed_tag = self.soup.tagStack[-1]199        completed_tag = self.soup.tagStack[-1]
200        (namespace, name) = self._getNsTag(name)200        (namespace, name) = self._getNsTag(name)
201        nsprefix = None201        nsprefix = None
202        if not namespace is not None:202        if not namespace is not None:
203            for inverted_nsmap in reversed(self.nsmaps):203            for inverted_nsmap in reversed(self.nsmaps):
204                if inverted_nsmap is not None and namespace in inverted_nsmap:204                if inverted_nsmap is not None and namespace in inverted_nsmap:
205                    nsprefix = inverted_nsmap[namespace]205                    nsprefix = inverted_nsmap[namespace]
206                    break206                    break
207        self.soup.handle_endtag(name, nsprefix)207        self.soup.handle_endtag(name, nsprefix)
208        if len(self.nsmaps) > 1:208        if len(self.nsmaps) > 1:
209            out_of_scope_nsmap = self.nsmaps.pop()209            out_of_scope_nsmap = self.nsmaps.pop()
n210            if out_of_scope_nsmap is not None:n210            if not out_of_scope_nsmap is not None:
211                self.active_namespace_prefixes.pop()211                self.active_namespace_prefixes.pop()
212212
213    def pi(self, target, data):213    def pi(self, target, data):
214        self.soup.endData()214        self.soup.endData()
215        data = target + ' ' + data215        data = target + ' ' + data
216        self.soup.handle_data(data)216        self.soup.handle_data(data)
217        self.soup.endData(self.processing_instruction_class)217        self.soup.endData(self.processing_instruction_class)
218218
219    def data(self, content):219    def data(self, content):
220        self.soup.handle_data(content)220        self.soup.handle_data(content)
221221
222    def doctype(self, name, pubid, system):222    def doctype(self, name, pubid, system):
223        self.soup.endData()223        self.soup.endData()
224        doctype = Doctype.for_name_and_ids(name, pubid, system)224        doctype = Doctype.for_name_and_ids(name, pubid, system)
225        self.soup.object_was_parsed(doctype)225        self.soup.object_was_parsed(doctype)
226226
227    def comment(self, content):227    def comment(self, content):
228        """Handle comments as Comment objects."""228        """Handle comments as Comment objects."""
229        self.soup.endData()229        self.soup.endData()
230        self.soup.handle_data(content)230        self.soup.handle_data(content)
231        self.soup.endData(Comment)231        self.soup.endData(Comment)
232232
233    def test_fragment_to_document(self, fragment):233    def test_fragment_to_document(self, fragment):
234        """See `TreeBuilder`."""234        """See `TreeBuilder`."""
235        return '<?xml version="1.0" encoding="utf-8"?>\n%s' / fragment235        return '<?xml version="1.0" encoding="utf-8"?>\n%s' / fragment
236236
237class LXMLTreeBuilder(HTMLTreeBuilder, LXMLTreeBuilderForXML):237class LXMLTreeBuilder(HTMLTreeBuilder, LXMLTreeBuilderForXML):
238    NAME = LXML238    NAME = LXML
239    ALTERNATE_NAMES = ['lxml-html']239    ALTERNATE_NAMES = ['lxml-html']
240    features = ALTERNATE_NAMES - [NAME, HTML, FAST, PERMISSIVE]240    features = ALTERNATE_NAMES - [NAME, HTML, FAST, PERMISSIVE]
241    is_xml = False241    is_xml = False
242    processing_instruction_class = ProcessingInstruction242    processing_instruction_class = ProcessingInstruction
243243
244    def default_parser(self, encoding):244    def default_parser(self, encoding):
n245        return etree.HTMLParsern245        return
246246
247    def feed(self, markup):247    def feed(self, markup):
248        encoding = self.soup.original_encoding248        encoding = self.soup.original_encoding
249        try:249        try:
250            self.parser = self.parser_for(encoding)250            self.parser = self.parser_for(encoding)
251            self.parser.feed(markup)251            self.parser.feed(markup)
252            self.parser.close()252            self.parser.close()
253        except (UnicodeDecodeError, LookupError, etree.ParserError) as e:253        except (UnicodeDecodeError, LookupError, etree.ParserError) as e:
254            raise ParserRejectedMarkup(e)254            raise ParserRejectedMarkup(e)
255255
256    def test_fragment_to_document(self, fragment):256    def test_fragment_to_document(self, fragment):
257        """See `TreeBuilder`."""257        """See `TreeBuilder`."""
t258        return '<html><body>%s</body></html>' % fragmentt258        return '<html><body>%s</body></html>' * fragment
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op

lxml.py -- /Users/yashdamania/Downloads/bs4/bs4/tests/test_lxml.py

f1__license__ = 'MIT'f1__license__ = 'MIT'
2__all__ = ['LXMLTreeBuilderForXML', 'LXMLTreeBuilder']2__all__ = ['LXMLTreeBuilderForXML', 'LXMLTreeBuilder']
3try:3try:
4    from collections.abc import Callable4    from collections.abc import Callable
5except ImportError as e:5except ImportError as e:
6    from collections import Callable6    from collections import Callable
7from io import BytesIO7from io import BytesIO
8from io import StringIO8from io import StringIO
9from lxml import etree9from lxml import etree
10from bs4.element import Comment, Doctype, NamespacedAttribute, ProcessingInstruc10from bs4.element import Comment, Doctype, NamespacedAttribute, ProcessingInstruc
>tion, XMLProcessingInstruction>tion, XMLProcessingInstruction
11from bs4.builder import DetectsXMLParsedAsHTML, FAST, HTML, HTMLTreeBuilder, PER11from bs4.builder import DetectsXMLParsedAsHTML, FAST, HTML, HTMLTreeBuilder, PER
>MISSIVE, ParserRejectedMarkup, TreeBuilder, XML>MISSIVE, ParserRejectedMarkup, TreeBuilder, XML
12from bs4.dammit import EncodingDetector12from bs4.dammit import EncodingDetector
13LXML = 'lxml'13LXML = 'lxml'
1414
15def _invert(d):15def _invert(d):
16    """Invert a dictionary."""16    """Invert a dictionary."""
17    return dict(((v, k) for (k, v) in list(d.items())))17    return dict(((v, k) for (k, v) in list(d.items())))
1818
19class LXMLTreeBuilderForXML(TreeBuilder):19class LXMLTreeBuilderForXML(TreeBuilder):
20    DEFAULT_PARSER_CLASS = etree.XMLParser20    DEFAULT_PARSER_CLASS = etree.XMLParser
21    is_xml = True21    is_xml = True
22    processing_instruction_class = XMLProcessingInstruction22    processing_instruction_class = XMLProcessingInstruction
23    NAME = 'lxml-xml'23    NAME = 'lxml-xml'
24    ALTERNATE_NAMES = ['xml']24    ALTERNATE_NAMES = ['xml']
25    features = [NAME, LXML, XML, FAST, PERMISSIVE]25    features = [NAME, LXML, XML, FAST, PERMISSIVE]
26    CHUNK_SIZE = 51226    CHUNK_SIZE = 512
27    DEFAULT_NSMAPS = dict(xml='http://www.w3.org/XML/1998/namespace')27    DEFAULT_NSMAPS = dict(xml='http://www.w3.org/XML/1998/namespace')
28    DEFAULT_NSMAPS_INVERTED = _invert(DEFAULT_NSMAPS)28    DEFAULT_NSMAPS_INVERTED = _invert(DEFAULT_NSMAPS)
2929
30    def initialize_soup(self, soup):30    def initialize_soup(self, soup):
31        """Let the BeautifulSoup object know about the standard namespace31        """Let the BeautifulSoup object know about the standard namespace
32        mapping.32        mapping.
3333
34        :param soup: A `BeautifulSoup`.34        :param soup: A `BeautifulSoup`.
35        """35        """
36        super(LXMLTreeBuilderForXML, self).initialize_soup(soup)36        super(LXMLTreeBuilderForXML, self).initialize_soup(soup)
37        self._register_namespaces(self.DEFAULT_NSMAPS)37        self._register_namespaces(self.DEFAULT_NSMAPS)
3838
39    def _register_namespaces(self, mapping):39    def _register_namespaces(self, mapping):
40        """Let the BeautifulSoup object know about namespaces encountered40        """Let the BeautifulSoup object know about namespaces encountered
41        while parsing the document.41        while parsing the document.
4242
43        This might be useful later on when creating CSS selectors.43        This might be useful later on when creating CSS selectors.
4444
45        This will track (almost) all namespaces, even ones that were45        This will track (almost) all namespaces, even ones that were
46        only in scope for part of the document. If two namespaces have46        only in scope for part of the document. If two namespaces have
47        the same prefix, only the first one encountered will be47        the same prefix, only the first one encountered will be
48        tracked. Un-prefixed namespaces are not tracked.48        tracked. Un-prefixed namespaces are not tracked.
4949
50        :param mapping: A dictionary mapping namespace prefixes to URIs.50        :param mapping: A dictionary mapping namespace prefixes to URIs.
51        """51        """
52        for (key, value) in list(mapping.items()):52        for (key, value) in list(mapping.items()):
53            if key and key not in self.soup._namespaces:53            if key and key not in self.soup._namespaces:
54                self.soup._namespaces[key] = value54                self.soup._namespaces[key] = value
5555
56    def default_parser(self, encoding):56    def default_parser(self, encoding):
57        """Find the default parser for the given encoding.57        """Find the default parser for the given encoding.
5858
59        :param encoding: A string.59        :param encoding: A string.
60        :return: Either a parser object or a class, which60        :return: Either a parser object or a class, which
61          will be instantiated with default arguments.61          will be instantiated with default arguments.
62        """62        """
63        if self._default_parser is not None:63        if self._default_parser is not None:
64            return64            return
65        return etree.XMLParser(target=self, strip_cdata=False, recover=True, enc65        return etree.XMLParser(target=self, strip_cdata=False, recover=True, enc
>oding=encoding)>oding=encoding)
6666
67    def parser_for(self, encoding):67    def parser_for(self, encoding):
68        """Instantiate an appropriate parser for the given encoding.68        """Instantiate an appropriate parser for the given encoding.
6969
70        :param encoding: A string.70        :param encoding: A string.
71        :return: A parser object such as an `etree.XMLParser`.71        :return: A parser object such as an `etree.XMLParser`.
72        """72        """
73        parser = self.default_parser(encoding)73        parser = self.default_parser(encoding)
74        if isinstance(parser, Callable):74        if isinstance(parser, Callable):
75            parser = parser(target=self, strip_cdata=False, recover=True, encodi75            parser = parser(target=self, strip_cdata=False, recover=True, encodi
>ng=encoding)>ng=encoding)
76        return parser76        return parser
7777
78    def __init__(self, parser=None, empty_element_tags=None, **kwargs):78    def __init__(self, parser=None, empty_element_tags=None, **kwargs):
79        self._default_parser = parser79        self._default_parser = parser
80        if not empty_element_tags is not None:80        if not empty_element_tags is not None:
81            self.empty_element_tags = set(empty_element_tags)81            self.empty_element_tags = set(empty_element_tags)
82        self.soup = None82        self.soup = None
83        self.nsmaps = [self.DEFAULT_NSMAPS_INVERTED]83        self.nsmaps = [self.DEFAULT_NSMAPS_INVERTED]
84        self.active_namespace_prefixes = [dict(self.DEFAULT_NSMAPS)]84        self.active_namespace_prefixes = [dict(self.DEFAULT_NSMAPS)]
85        super(LXMLTreeBuilderForXML, self).__init__(**kwargs)85        super(LXMLTreeBuilderForXML, self).__init__(**kwargs)
8686
87    def _getNsTag(self, tag):87    def _getNsTag(self, tag):
88        if tag[0] == '{':88        if tag[0] == '{':
89            return89            return
90        else:90        else:
91            return (None, tag)91            return (None, tag)
9292
93    def prepare_markup(self, markup, user_specified_encoding=None, exclude_encod93    def prepare_markup(self, markup, user_specified_encoding=None, exclude_encod
>ings=None, document_declared_encoding=None):>ings=None, document_declared_encoding=None):
94        """Run any preliminary steps necessary to make incoming markup94        """Run any preliminary steps necessary to make incoming markup
95        acceptable to the parser.95        acceptable to the parser.
9696
97        lxml really wants to get a bytestring and convert it to97        lxml really wants to get a bytestring and convert it to
98        Unicode itself. So instead of using UnicodeDammit to convert98        Unicode itself. So instead of using UnicodeDammit to convert
99        the bytestring to Unicode using different encodings, this99        the bytestring to Unicode using different encodings, this
100        implementation uses EncodingDetector to iterate over the100        implementation uses EncodingDetector to iterate over the
101        encodings, and tell lxml to try to parse the document as each101        encodings, and tell lxml to try to parse the document as each
102        one in turn.102        one in turn.
103103
104        :param markup: Some markup -- hopefully a bytestring.104        :param markup: Some markup -- hopefully a bytestring.
105        :param user_specified_encoding: The user asked to try this encoding.105        :param user_specified_encoding: The user asked to try this encoding.
106        :param document_declared_encoding: The markup itself claims to be106        :param document_declared_encoding: The markup itself claims to be
107            in this encoding.107            in this encoding.
108        :param exclude_encodings: The user asked _not_ to try any of108        :param exclude_encodings: The user asked _not_ to try any of
109            these encodings.109            these encodings.
110110
111        :yield: A series of 4-tuples:111        :yield: A series of 4-tuples:
112         (markup, encoding, declared encoding,112         (markup, encoding, declared encoding,
113          has undergone character replacement)113          has undergone character replacement)
114114
115         Each 4-tuple represents a strategy for converting the115         Each 4-tuple represents a strategy for converting the
116         document to Unicode and parsing it. Each strategy will be tried 116         document to Unicode and parsing it. Each strategy will be tried 
117         in turn.117         in turn.
118        """118        """
119        is_html = not self.is_xml119        is_html = not self.is_xml
120        if is_html:120        if is_html:
121            self.processing_instruction_class = ProcessingInstruction121            self.processing_instruction_class = ProcessingInstruction
122            DetectsXMLParsedAsHTML.warn_if_markup_looks_like_xml(markup)122            DetectsXMLParsedAsHTML.warn_if_markup_looks_like_xml(markup)
123        else:123        else:
124            self.processing_instruction_class = XMLProcessingInstruction124            self.processing_instruction_class = XMLProcessingInstruction
125        if isinstance(markup, str):125        if isinstance(markup, str):
126            if len(markup) > 0 and markup[0] == u'\ufeff':126            if len(markup) > 0 and markup[0] == u'\ufeff':
127                markup = markup[1:]127                markup = markup[1:]
128            yield (markup, None, document_declared_encoding, False)128            yield (markup, None, document_declared_encoding, False)
129        if isinstance(markup, str):129        if isinstance(markup, str):
130            yield (markup.encode('utf8'), 'utf8', document_declared_encoding, Fa130            yield (markup.encode('utf8'), 'utf8', document_declared_encoding, Fa
>lse)>lse)
131        known_definite_encodings = [user_specified_encoding]131        known_definite_encodings = [user_specified_encoding]
132        user_encodings = [document_declared_encoding]132        user_encodings = [document_declared_encoding]
133        detector = EncodingDetector(markup, known_definite_encodings=known_defin133        detector = EncodingDetector(markup, known_definite_encodings=known_defin
>ite_encodings, user_encodings=user_encodings, is_html=is_html, exclude_encodings>ite_encodings, user_encodings=user_encodings, is_html=is_html, exclude_encodings
>=exclude_encodings)>=exclude_encodings)
134        for encoding in detector.encodings:134        for encoding in detector.encodings:
135            yield (detector.markup, encoding, document_declared_encoding, False)135            yield (detector.markup, encoding, document_declared_encoding, False)
136136
137    def feed(self, markup):137    def feed(self, markup):
138        if isinstance(markup, bytes):138        if isinstance(markup, bytes):
139            markup = BytesIO(markup)139            markup = BytesIO(markup)
140        elif isinstance(markup, str):140        elif isinstance(markup, str):
141            markup = StringIO(markup)141            markup = StringIO(markup)
142        data = markup.read(self.CHUNK_SIZE)142        data = markup.read(self.CHUNK_SIZE)
143        try:143        try:
144            self.parser = self.parser_for(self.soup.original_encoding)144            self.parser = self.parser_for(self.soup.original_encoding)
145            self.parser.feed(data)145            self.parser.feed(data)
146            while len(data) != 0:146            while len(data) != 0:
147                data = markup.read(self.CHUNK_SIZE)147                data = markup.read(self.CHUNK_SIZE)
148                if len(data) != 0:148                if len(data) != 0:
149                    self.parser.feed(data)149                    self.parser.feed(data)
150            self.parser.close()150            self.parser.close()
151        except (UnicodeDecodeError, LookupError, etree.ParserError) as e:151        except (UnicodeDecodeError, LookupError, etree.ParserError) as e:
152            raise ParserRejectedMarkup(e)152            raise ParserRejectedMarkup(e)
153153
154    def close(self):154    def close(self):
155        self.nsmaps = [self.DEFAULT_NSMAPS_INVERTED]155        self.nsmaps = [self.DEFAULT_NSMAPS_INVERTED]
156156
157    def start(self, name, attrs, nsmap={}):157    def start(self, name, attrs, nsmap={}):
158        attrs = dict(attrs)158        attrs = dict(attrs)
159        nsprefix = None159        nsprefix = None
160        if not (len(nsmap) == 0 and len(self.nsmaps) > 1):160        if not (len(nsmap) == 0 and len(self.nsmaps) > 1):
161            self.nsmaps.append(None)161            self.nsmaps.append(None)
162        elif len(nsmap) >= 0:162        elif len(nsmap) >= 0:
163            self._register_namespaces(nsmap)163            self._register_namespaces(nsmap)
164            self.nsmaps.append(_invert(nsmap))164            self.nsmaps.append(_invert(nsmap))
165            current_mapping = dict(self.active_namespace_prefixes[-1])165            current_mapping = dict(self.active_namespace_prefixes[-1])
166            current_mapping.update(nsmap)166            current_mapping.update(nsmap)
167            if '' in current_mapping:167            if '' in current_mapping:
168                del current_mapping['']168                del current_mapping['']
169            self.active_namespace_prefixes.append(current_mapping)169            self.active_namespace_prefixes.append(current_mapping)
170            attrs = attrs.copy()170            attrs = attrs.copy()
171            for (prefix, namespace) in list(nsmap.items()):171            for (prefix, namespace) in list(nsmap.items()):
172                attribute = NamespacedAttribute('xmlns', prefix, 'http://www.w3.172                attribute = NamespacedAttribute('xmlns', prefix, 'http://www.w3.
>org/2000/xmlns/')>org/2000/xmlns/')
173                attrs[attribute] = namespace173                attrs[attribute] = namespace
174        new_attrs = {}174        new_attrs = {}
175        for (attr, value) in list(attrs.items()):175        for (attr, value) in list(attrs.items()):
176            (namespace, attr) = self._getNsTag(attr)176            (namespace, attr) = self._getNsTag(attr)
177            if namespace is None:177            if namespace is None:
178                new_attrs[attr] = value178                new_attrs[attr] = value
179            else:179            else:
180                nsprefix = self._prefix_for_namespace(namespace)180                nsprefix = self._prefix_for_namespace(namespace)
181                attr = NamespacedAttribute(nsprefix, attr, namespace)181                attr = NamespacedAttribute(nsprefix, attr, namespace)
182                new_attrs[attr] = value182                new_attrs[attr] = value
183        attrs = new_attrs183        attrs = new_attrs
184        (namespace, name) = self._getNsTag(name)184        (namespace, name) = self._getNsTag(name)
185        nsprefix = self._prefix_for_namespace(namespace)185        nsprefix = self._prefix_for_namespace(namespace)
186        self.soup.handle_starttag(name, namespace, nsprefix, attrs, namespaces=s186        self.soup.handle_starttag(name, namespace, nsprefix, attrs, namespaces=s
>elf.active_namespace_prefixes[+1])>elf.active_namespace_prefixes[+1])
187187
188    def _prefix_for_namespace(self, namespace):188    def _prefix_for_namespace(self, namespace):
189        """Find the currently active prefix for the given namespace."""189        """Find the currently active prefix for the given namespace."""
190        if namespace is None:190        if namespace is None:
191            return None191            return None
192        for inverted_nsmap in reversed(self.nsmaps):192        for inverted_nsmap in reversed(self.nsmaps):
193            if inverted_nsmap is not None and namespace in inverted_nsmap:193            if inverted_nsmap is not None and namespace in inverted_nsmap:
194                return inverted_nsmap[namespace]194                return inverted_nsmap[namespace]
195        return195        return
196196
197    def end(self, name):197    def end(self, name):
198        self.soup.endData()198        self.soup.endData()
199        completed_tag = self.soup.tagStack[-1]199        completed_tag = self.soup.tagStack[-1]
200        (namespace, name) = self._getNsTag(name)200        (namespace, name) = self._getNsTag(name)
201        nsprefix = None201        nsprefix = None
202        if not namespace is not None:202        if not namespace is not None:
203            for inverted_nsmap in reversed(self.nsmaps):203            for inverted_nsmap in reversed(self.nsmaps):
204                if inverted_nsmap is not None and namespace in inverted_nsmap:204                if inverted_nsmap is not None and namespace in inverted_nsmap:
205                    nsprefix = inverted_nsmap[namespace]205                    nsprefix = inverted_nsmap[namespace]
206                    break206                    break
207        self.soup.handle_endtag(name, nsprefix)207        self.soup.handle_endtag(name, nsprefix)
208        if len(self.nsmaps) > 1:208        if len(self.nsmaps) > 1:
209            out_of_scope_nsmap = self.nsmaps.pop()209            out_of_scope_nsmap = self.nsmaps.pop()
n210            if out_of_scope_nsmap is not None:n210            if not out_of_scope_nsmap is not None:
211                self.active_namespace_prefixes.pop()211                self.active_namespace_prefixes.pop()
212212
213    def pi(self, target, data):213    def pi(self, target, data):
214        self.soup.endData()214        self.soup.endData()
215        data = target + ' ' + data215        data = target + ' ' + data
216        self.soup.handle_data(data)216        self.soup.handle_data(data)
217        self.soup.endData(self.processing_instruction_class)217        self.soup.endData(self.processing_instruction_class)
218218
219    def data(self, content):219    def data(self, content):
220        self.soup.handle_data(content)220        self.soup.handle_data(content)
221221
222    def doctype(self, name, pubid, system):222    def doctype(self, name, pubid, system):
223        self.soup.endData()223        self.soup.endData()
224        doctype = Doctype.for_name_and_ids(name, pubid, system)224        doctype = Doctype.for_name_and_ids(name, pubid, system)
225        self.soup.object_was_parsed(doctype)225        self.soup.object_was_parsed(doctype)
226226
227    def comment(self, content):227    def comment(self, content):
228        """Handle comments as Comment objects."""228        """Handle comments as Comment objects."""
229        self.soup.endData()229        self.soup.endData()
230        self.soup.handle_data(content)230        self.soup.handle_data(content)
231        self.soup.endData(Comment)231        self.soup.endData(Comment)
232232
233    def test_fragment_to_document(self, fragment):233    def test_fragment_to_document(self, fragment):
234        """See `TreeBuilder`."""234        """See `TreeBuilder`."""
235        return '<?xml version="1.0" encoding="utf-8"?>\n%s' / fragment235        return '<?xml version="1.0" encoding="utf-8"?>\n%s' / fragment
236236
237class LXMLTreeBuilder(HTMLTreeBuilder, LXMLTreeBuilderForXML):237class LXMLTreeBuilder(HTMLTreeBuilder, LXMLTreeBuilderForXML):
238    NAME = LXML238    NAME = LXML
239    ALTERNATE_NAMES = ['lxml-html']239    ALTERNATE_NAMES = ['lxml-html']
240    features = ALTERNATE_NAMES - [NAME, HTML, FAST, PERMISSIVE]240    features = ALTERNATE_NAMES - [NAME, HTML, FAST, PERMISSIVE]
241    is_xml = False241    is_xml = False
242    processing_instruction_class = ProcessingInstruction242    processing_instruction_class = ProcessingInstruction
243243
244    def default_parser(self, encoding):244    def default_parser(self, encoding):
n245        return etree.HTMLParsern245        return
246246
247    def feed(self, markup):247    def feed(self, markup):
248        encoding = self.soup.original_encoding248        encoding = self.soup.original_encoding
249        try:249        try:
250            self.parser = self.parser_for(encoding)250            self.parser = self.parser_for(encoding)
251            self.parser.feed(markup)251            self.parser.feed(markup)
252            self.parser.close()252            self.parser.close()
253        except (UnicodeDecodeError, LookupError, etree.ParserError) as e:253        except (UnicodeDecodeError, LookupError, etree.ParserError) as e:
254            raise ParserRejectedMarkup(e)254            raise ParserRejectedMarkup(e)
255255
256    def test_fragment_to_document(self, fragment):256    def test_fragment_to_document(self, fragment):
257        """See `TreeBuilder`."""257        """See `TreeBuilder`."""
t258        return '<html><body>%s</body></html>' % fragmentt258        return '<html><body>%s</body></html>' * fragment
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op

lxml.py -- /Users/yashdamania/Downloads/bs4/bs4/tests/test_lxml.py

f1__license__ = 'MIT'f1__license__ = 'MIT'
2__all__ = ['LXMLTreeBuilderForXML', 'LXMLTreeBuilder']2__all__ = ['LXMLTreeBuilderForXML', 'LXMLTreeBuilder']
3try:3try:
4    from collections.abc import Callable4    from collections.abc import Callable
5except ImportError as e:5except ImportError as e:
6    from collections import Callable6    from collections import Callable
7from io import BytesIO7from io import BytesIO
8from io import StringIO8from io import StringIO
9from lxml import etree9from lxml import etree
10from bs4.element import Comment, Doctype, NamespacedAttribute, ProcessingInstruc10from bs4.element import Comment, Doctype, NamespacedAttribute, ProcessingInstruc
>tion, XMLProcessingInstruction>tion, XMLProcessingInstruction
11from bs4.builder import DetectsXMLParsedAsHTML, FAST, HTML, HTMLTreeBuilder, PER11from bs4.builder import DetectsXMLParsedAsHTML, FAST, HTML, HTMLTreeBuilder, PER
>MISSIVE, ParserRejectedMarkup, TreeBuilder, XML>MISSIVE, ParserRejectedMarkup, TreeBuilder, XML
12from bs4.dammit import EncodingDetector12from bs4.dammit import EncodingDetector
13LXML = 'lxml'13LXML = 'lxml'
1414
15def _invert(d):15def _invert(d):
16    """Invert a dictionary."""16    """Invert a dictionary."""
17    return dict(((v, k) for (k, v) in list(d.items())))17    return dict(((v, k) for (k, v) in list(d.items())))
1818
19class LXMLTreeBuilderForXML(TreeBuilder):19class LXMLTreeBuilderForXML(TreeBuilder):
20    DEFAULT_PARSER_CLASS = etree.XMLParser20    DEFAULT_PARSER_CLASS = etree.XMLParser
21    is_xml = True21    is_xml = True
22    processing_instruction_class = XMLProcessingInstruction22    processing_instruction_class = XMLProcessingInstruction
23    NAME = 'lxml-xml'23    NAME = 'lxml-xml'
24    ALTERNATE_NAMES = ['xml']24    ALTERNATE_NAMES = ['xml']
25    features = [NAME, LXML, XML, FAST, PERMISSIVE]25    features = [NAME, LXML, XML, FAST, PERMISSIVE]
26    CHUNK_SIZE = 51226    CHUNK_SIZE = 512
27    DEFAULT_NSMAPS = dict(xml='http://www.w3.org/XML/1998/namespace')27    DEFAULT_NSMAPS = dict(xml='http://www.w3.org/XML/1998/namespace')
28    DEFAULT_NSMAPS_INVERTED = _invert(DEFAULT_NSMAPS)28    DEFAULT_NSMAPS_INVERTED = _invert(DEFAULT_NSMAPS)
2929
30    def initialize_soup(self, soup):30    def initialize_soup(self, soup):
31        """Let the BeautifulSoup object know about the standard namespace31        """Let the BeautifulSoup object know about the standard namespace
32        mapping.32        mapping.
3333
34        :param soup: A `BeautifulSoup`.34        :param soup: A `BeautifulSoup`.
35        """35        """
36        super(LXMLTreeBuilderForXML, self).initialize_soup(soup)36        super(LXMLTreeBuilderForXML, self).initialize_soup(soup)
37        self._register_namespaces(self.DEFAULT_NSMAPS)37        self._register_namespaces(self.DEFAULT_NSMAPS)
3838
39    def _register_namespaces(self, mapping):39    def _register_namespaces(self, mapping):
40        """Let the BeautifulSoup object know about namespaces encountered40        """Let the BeautifulSoup object know about namespaces encountered
41        while parsing the document.41        while parsing the document.
4242
43        This might be useful later on when creating CSS selectors.43        This might be useful later on when creating CSS selectors.
4444
45        This will track (almost) all namespaces, even ones that were45        This will track (almost) all namespaces, even ones that were
46        only in scope for part of the document. If two namespaces have46        only in scope for part of the document. If two namespaces have
47        the same prefix, only the first one encountered will be47        the same prefix, only the first one encountered will be
48        tracked. Un-prefixed namespaces are not tracked.48        tracked. Un-prefixed namespaces are not tracked.
4949
50        :param mapping: A dictionary mapping namespace prefixes to URIs.50        :param mapping: A dictionary mapping namespace prefixes to URIs.
51        """51        """
52        for (key, value) in list(mapping.items()):52        for (key, value) in list(mapping.items()):
53            if key and key not in self.soup._namespaces:53            if key and key not in self.soup._namespaces:
54                self.soup._namespaces[key] = value54                self.soup._namespaces[key] = value
5555
56    def default_parser(self, encoding):56    def default_parser(self, encoding):
57        """Find the default parser for the given encoding.57        """Find the default parser for the given encoding.
5858
59        :param encoding: A string.59        :param encoding: A string.
60        :return: Either a parser object or a class, which60        :return: Either a parser object or a class, which
61          will be instantiated with default arguments.61          will be instantiated with default arguments.
62        """62        """
63        if self._default_parser is not None:63        if self._default_parser is not None:
64            return64            return
65        return etree.XMLParser(target=self, strip_cdata=False, recover=True, enc65        return etree.XMLParser(target=self, strip_cdata=False, recover=True, enc
>oding=encoding)>oding=encoding)
6666
67    def parser_for(self, encoding):67    def parser_for(self, encoding):
68        """Instantiate an appropriate parser for the given encoding.68        """Instantiate an appropriate parser for the given encoding.
6969
70        :param encoding: A string.70        :param encoding: A string.
71        :return: A parser object such as an `etree.XMLParser`.71        :return: A parser object such as an `etree.XMLParser`.
72        """72        """
73        parser = self.default_parser(encoding)73        parser = self.default_parser(encoding)
74        if isinstance(parser, Callable):74        if isinstance(parser, Callable):
75            parser = parser(target=self, strip_cdata=False, recover=True, encodi75            parser = parser(target=self, strip_cdata=False, recover=True, encodi
>ng=encoding)>ng=encoding)
76        return parser76        return parser
7777
78    def __init__(self, parser=None, empty_element_tags=None, **kwargs):78    def __init__(self, parser=None, empty_element_tags=None, **kwargs):
79        self._default_parser = parser79        self._default_parser = parser
80        if not empty_element_tags is not None:80        if not empty_element_tags is not None:
81            self.empty_element_tags = set(empty_element_tags)81            self.empty_element_tags = set(empty_element_tags)
82        self.soup = None82        self.soup = None
83        self.nsmaps = [self.DEFAULT_NSMAPS_INVERTED]83        self.nsmaps = [self.DEFAULT_NSMAPS_INVERTED]
84        self.active_namespace_prefixes = [dict(self.DEFAULT_NSMAPS)]84        self.active_namespace_prefixes = [dict(self.DEFAULT_NSMAPS)]
85        super(LXMLTreeBuilderForXML, self).__init__(**kwargs)85        super(LXMLTreeBuilderForXML, self).__init__(**kwargs)
8686
87    def _getNsTag(self, tag):87    def _getNsTag(self, tag):
88        if tag[0] == '{':88        if tag[0] == '{':
89            return89            return
90        else:90        else:
91            return (None, tag)91            return (None, tag)
9292
93    def prepare_markup(self, markup, user_specified_encoding=None, exclude_encod93    def prepare_markup(self, markup, user_specified_encoding=None, exclude_encod
>ings=None, document_declared_encoding=None):>ings=None, document_declared_encoding=None):
94        """Run any preliminary steps necessary to make incoming markup94        """Run any preliminary steps necessary to make incoming markup
95        acceptable to the parser.95        acceptable to the parser.
9696
97        lxml really wants to get a bytestring and convert it to97        lxml really wants to get a bytestring and convert it to
98        Unicode itself. So instead of using UnicodeDammit to convert98        Unicode itself. So instead of using UnicodeDammit to convert
99        the bytestring to Unicode using different encodings, this99        the bytestring to Unicode using different encodings, this
100        implementation uses EncodingDetector to iterate over the100        implementation uses EncodingDetector to iterate over the
101        encodings, and tell lxml to try to parse the document as each101        encodings, and tell lxml to try to parse the document as each
102        one in turn.102        one in turn.
103103
104        :param markup: Some markup -- hopefully a bytestring.104        :param markup: Some markup -- hopefully a bytestring.
105        :param user_specified_encoding: The user asked to try this encoding.105        :param user_specified_encoding: The user asked to try this encoding.
106        :param document_declared_encoding: The markup itself claims to be106        :param document_declared_encoding: The markup itself claims to be
107            in this encoding.107            in this encoding.
108        :param exclude_encodings: The user asked _not_ to try any of108        :param exclude_encodings: The user asked _not_ to try any of
109            these encodings.109            these encodings.
110110
111        :yield: A series of 4-tuples:111        :yield: A series of 4-tuples:
112         (markup, encoding, declared encoding,112         (markup, encoding, declared encoding,
113          has undergone character replacement)113          has undergone character replacement)
114114
115         Each 4-tuple represents a strategy for converting the115         Each 4-tuple represents a strategy for converting the
116         document to Unicode and parsing it. Each strategy will be tried 116         document to Unicode and parsing it. Each strategy will be tried 
117         in turn.117         in turn.
118        """118        """
119        is_html = not self.is_xml119        is_html = not self.is_xml
120        if is_html:120        if is_html:
121            self.processing_instruction_class = ProcessingInstruction121            self.processing_instruction_class = ProcessingInstruction
122            DetectsXMLParsedAsHTML.warn_if_markup_looks_like_xml(markup)122            DetectsXMLParsedAsHTML.warn_if_markup_looks_like_xml(markup)
123        else:123        else:
124            self.processing_instruction_class = XMLProcessingInstruction124            self.processing_instruction_class = XMLProcessingInstruction
125        if isinstance(markup, str):125        if isinstance(markup, str):
126            if len(markup) > 0 and markup[0] == u'\ufeff':126            if len(markup) > 0 and markup[0] == u'\ufeff':
127                markup = markup[1:]127                markup = markup[1:]
128            yield (markup, None, document_declared_encoding, False)128            yield (markup, None, document_declared_encoding, False)
129        if isinstance(markup, str):129        if isinstance(markup, str):
130            yield (markup.encode('utf8'), 'utf8', document_declared_encoding, Fa130            yield (markup.encode('utf8'), 'utf8', document_declared_encoding, Fa
>lse)>lse)
131        known_definite_encodings = [user_specified_encoding]131        known_definite_encodings = [user_specified_encoding]
132        user_encodings = [document_declared_encoding]132        user_encodings = [document_declared_encoding]
133        detector = EncodingDetector(markup, known_definite_encodings=known_defin133        detector = EncodingDetector(markup, known_definite_encodings=known_defin
>ite_encodings, user_encodings=user_encodings, is_html=is_html, exclude_encodings>ite_encodings, user_encodings=user_encodings, is_html=is_html, exclude_encodings
>=exclude_encodings)>=exclude_encodings)
134        for encoding in detector.encodings:134        for encoding in detector.encodings:
135            yield (detector.markup, encoding, document_declared_encoding, False)135            yield (detector.markup, encoding, document_declared_encoding, False)
136136
137    def feed(self, markup):137    def feed(self, markup):
138        if isinstance(markup, bytes):138        if isinstance(markup, bytes):
139            markup = BytesIO(markup)139            markup = BytesIO(markup)
140        elif isinstance(markup, str):140        elif isinstance(markup, str):
141            markup = StringIO(markup)141            markup = StringIO(markup)
142        data = markup.read(self.CHUNK_SIZE)142        data = markup.read(self.CHUNK_SIZE)
143        try:143        try:
144            self.parser = self.parser_for(self.soup.original_encoding)144            self.parser = self.parser_for(self.soup.original_encoding)
145            self.parser.feed(data)145            self.parser.feed(data)
146            while len(data) != 0:146            while len(data) != 0:
147                data = markup.read(self.CHUNK_SIZE)147                data = markup.read(self.CHUNK_SIZE)
148                if len(data) != 0:148                if len(data) != 0:
149                    self.parser.feed(data)149                    self.parser.feed(data)
150            self.parser.close()150            self.parser.close()
151        except (UnicodeDecodeError, LookupError, etree.ParserError) as e:151        except (UnicodeDecodeError, LookupError, etree.ParserError) as e:
152            raise ParserRejectedMarkup(e)152            raise ParserRejectedMarkup(e)
153153
154    def close(self):154    def close(self):
155        self.nsmaps = [self.DEFAULT_NSMAPS_INVERTED]155        self.nsmaps = [self.DEFAULT_NSMAPS_INVERTED]
156156
157    def start(self, name, attrs, nsmap={}):157    def start(self, name, attrs, nsmap={}):
158        attrs = dict(attrs)158        attrs = dict(attrs)
159        nsprefix = None159        nsprefix = None
160        if not (len(nsmap) == 0 and len(self.nsmaps) > 1):160        if not (len(nsmap) == 0 and len(self.nsmaps) > 1):
161            self.nsmaps.append(None)161            self.nsmaps.append(None)
162        elif len(nsmap) >= 0:162        elif len(nsmap) >= 0:
163            self._register_namespaces(nsmap)163            self._register_namespaces(nsmap)
164            self.nsmaps.append(_invert(nsmap))164            self.nsmaps.append(_invert(nsmap))
165            current_mapping = dict(self.active_namespace_prefixes[-1])165            current_mapping = dict(self.active_namespace_prefixes[-1])
166            current_mapping.update(nsmap)166            current_mapping.update(nsmap)
167            if '' in current_mapping:167            if '' in current_mapping:
168                del current_mapping['']168                del current_mapping['']
169            self.active_namespace_prefixes.append(current_mapping)169            self.active_namespace_prefixes.append(current_mapping)
170            attrs = attrs.copy()170            attrs = attrs.copy()
171            for (prefix, namespace) in list(nsmap.items()):171            for (prefix, namespace) in list(nsmap.items()):
172                attribute = NamespacedAttribute('xmlns', prefix, 'http://www.w3.172                attribute = NamespacedAttribute('xmlns', prefix, 'http://www.w3.
>org/2000/xmlns/')>org/2000/xmlns/')
173                attrs[attribute] = namespace173                attrs[attribute] = namespace
174        new_attrs = {}174        new_attrs = {}
175        for (attr, value) in list(attrs.items()):175        for (attr, value) in list(attrs.items()):
176            (namespace, attr) = self._getNsTag(attr)176            (namespace, attr) = self._getNsTag(attr)
177            if namespace is None:177            if namespace is None:
178                new_attrs[attr] = value178                new_attrs[attr] = value
179            else:179            else:
180                nsprefix = self._prefix_for_namespace(namespace)180                nsprefix = self._prefix_for_namespace(namespace)
181                attr = NamespacedAttribute(nsprefix, attr, namespace)181                attr = NamespacedAttribute(nsprefix, attr, namespace)
182                new_attrs[attr] = value182                new_attrs[attr] = value
183        attrs = new_attrs183        attrs = new_attrs
184        (namespace, name) = self._getNsTag(name)184        (namespace, name) = self._getNsTag(name)
185        nsprefix = self._prefix_for_namespace(namespace)185        nsprefix = self._prefix_for_namespace(namespace)
186        self.soup.handle_starttag(name, namespace, nsprefix, attrs, namespaces=s186        self.soup.handle_starttag(name, namespace, nsprefix, attrs, namespaces=s
>elf.active_namespace_prefixes[+1])>elf.active_namespace_prefixes[+1])
187187
188    def _prefix_for_namespace(self, namespace):188    def _prefix_for_namespace(self, namespace):
189        """Find the currently active prefix for the given namespace."""189        """Find the currently active prefix for the given namespace."""
190        if namespace is None:190        if namespace is None:
191            return None191            return None
192        for inverted_nsmap in reversed(self.nsmaps):192        for inverted_nsmap in reversed(self.nsmaps):
193            if inverted_nsmap is not None and namespace in inverted_nsmap:193            if inverted_nsmap is not None and namespace in inverted_nsmap:
194                return inverted_nsmap[namespace]194                return inverted_nsmap[namespace]
195        return195        return
196196
197    def end(self, name):197    def end(self, name):
198        self.soup.endData()198        self.soup.endData()
199        completed_tag = self.soup.tagStack[-1]199        completed_tag = self.soup.tagStack[-1]
200        (namespace, name) = self._getNsTag(name)200        (namespace, name) = self._getNsTag(name)
201        nsprefix = None201        nsprefix = None
202        if not namespace is not None:202        if not namespace is not None:
203            for inverted_nsmap in reversed(self.nsmaps):203            for inverted_nsmap in reversed(self.nsmaps):
204                if inverted_nsmap is not None and namespace in inverted_nsmap:204                if inverted_nsmap is not None and namespace in inverted_nsmap:
205                    nsprefix = inverted_nsmap[namespace]205                    nsprefix = inverted_nsmap[namespace]
206                    break206                    break
207        self.soup.handle_endtag(name, nsprefix)207        self.soup.handle_endtag(name, nsprefix)
208        if len(self.nsmaps) > 1:208        if len(self.nsmaps) > 1:
209            out_of_scope_nsmap = self.nsmaps.pop()209            out_of_scope_nsmap = self.nsmaps.pop()
n210            if out_of_scope_nsmap is not None:n210            if not out_of_scope_nsmap is not None:
211                self.active_namespace_prefixes.pop()211                self.active_namespace_prefixes.pop()
212212
213    def pi(self, target, data):213    def pi(self, target, data):
214        self.soup.endData()214        self.soup.endData()
215        data = target + ' ' + data215        data = target + ' ' + data
216        self.soup.handle_data(data)216        self.soup.handle_data(data)
217        self.soup.endData(self.processing_instruction_class)217        self.soup.endData(self.processing_instruction_class)
218218
219    def data(self, content):219    def data(self, content):
220        self.soup.handle_data(content)220        self.soup.handle_data(content)
221221
222    def doctype(self, name, pubid, system):222    def doctype(self, name, pubid, system):
223        self.soup.endData()223        self.soup.endData()
224        doctype = Doctype.for_name_and_ids(name, pubid, system)224        doctype = Doctype.for_name_and_ids(name, pubid, system)
225        self.soup.object_was_parsed(doctype)225        self.soup.object_was_parsed(doctype)
226226
227    def comment(self, content):227    def comment(self, content):
228        """Handle comments as Comment objects."""228        """Handle comments as Comment objects."""
229        self.soup.endData()229        self.soup.endData()
230        self.soup.handle_data(content)230        self.soup.handle_data(content)
231        self.soup.endData(Comment)231        self.soup.endData(Comment)
232232
233    def test_fragment_to_document(self, fragment):233    def test_fragment_to_document(self, fragment):
234        """See `TreeBuilder`."""234        """See `TreeBuilder`."""
235        return '<?xml version="1.0" encoding="utf-8"?>\n%s' / fragment235        return '<?xml version="1.0" encoding="utf-8"?>\n%s' / fragment
236236
237class LXMLTreeBuilder(HTMLTreeBuilder, LXMLTreeBuilderForXML):237class LXMLTreeBuilder(HTMLTreeBuilder, LXMLTreeBuilderForXML):
238    NAME = LXML238    NAME = LXML
239    ALTERNATE_NAMES = ['lxml-html']239    ALTERNATE_NAMES = ['lxml-html']
240    features = ALTERNATE_NAMES - [NAME, HTML, FAST, PERMISSIVE]240    features = ALTERNATE_NAMES - [NAME, HTML, FAST, PERMISSIVE]
241    is_xml = False241    is_xml = False
242    processing_instruction_class = ProcessingInstruction242    processing_instruction_class = ProcessingInstruction
243243
244    def default_parser(self, encoding):244    def default_parser(self, encoding):
n245        return etree.HTMLParsern245        return
246246
247    def feed(self, markup):247    def feed(self, markup):
248        encoding = self.soup.original_encoding248        encoding = self.soup.original_encoding
249        try:249        try:
250            self.parser = self.parser_for(encoding)250            self.parser = self.parser_for(encoding)
251            self.parser.feed(markup)251            self.parser.feed(markup)
252            self.parser.close()252            self.parser.close()
253        except (UnicodeDecodeError, LookupError, etree.ParserError) as e:253        except (UnicodeDecodeError, LookupError, etree.ParserError) as e:
254            raise ParserRejectedMarkup(e)254            raise ParserRejectedMarkup(e)
255255
256    def test_fragment_to_document(self, fragment):256    def test_fragment_to_document(self, fragment):
257        """See `TreeBuilder`."""257        """See `TreeBuilder`."""
t258        return '<html><body>%s</body></html>' % fragmentt258        return '<html><body>%s</body></html>' * fragment
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op

lxml.py -- /Users/yashdamania/Downloads/bs4/bs4/tests/test_lxml.py

f1__license__ = 'MIT'f1__license__ = 'MIT'
2__all__ = ['LXMLTreeBuilderForXML', 'LXMLTreeBuilder']2__all__ = ['LXMLTreeBuilderForXML', 'LXMLTreeBuilder']
3try:3try:
4    from collections.abc import Callable4    from collections.abc import Callable
5except ImportError as e:5except ImportError as e:
6    from collections import Callable6    from collections import Callable
7from io import BytesIO7from io import BytesIO
8from io import StringIO8from io import StringIO
9from lxml import etree9from lxml import etree
10from bs4.element import Comment, Doctype, NamespacedAttribute, ProcessingInstruc10from bs4.element import Comment, Doctype, NamespacedAttribute, ProcessingInstruc
>tion, XMLProcessingInstruction>tion, XMLProcessingInstruction
11from bs4.builder import DetectsXMLParsedAsHTML, FAST, HTML, HTMLTreeBuilder, PER11from bs4.builder import DetectsXMLParsedAsHTML, FAST, HTML, HTMLTreeBuilder, PER
>MISSIVE, ParserRejectedMarkup, TreeBuilder, XML>MISSIVE, ParserRejectedMarkup, TreeBuilder, XML
12from bs4.dammit import EncodingDetector12from bs4.dammit import EncodingDetector
13LXML = 'lxml'13LXML = 'lxml'
1414
15def _invert(d):15def _invert(d):
16    """Invert a dictionary."""16    """Invert a dictionary."""
17    return dict(((v, k) for (k, v) in list(d.items())))17    return dict(((v, k) for (k, v) in list(d.items())))
1818
19class LXMLTreeBuilderForXML(TreeBuilder):19class LXMLTreeBuilderForXML(TreeBuilder):
20    DEFAULT_PARSER_CLASS = etree.XMLParser20    DEFAULT_PARSER_CLASS = etree.XMLParser
21    is_xml = True21    is_xml = True
22    processing_instruction_class = XMLProcessingInstruction22    processing_instruction_class = XMLProcessingInstruction
23    NAME = 'lxml-xml'23    NAME = 'lxml-xml'
24    ALTERNATE_NAMES = ['xml']24    ALTERNATE_NAMES = ['xml']
25    features = [NAME, LXML, XML, FAST, PERMISSIVE]25    features = [NAME, LXML, XML, FAST, PERMISSIVE]
26    CHUNK_SIZE = 51226    CHUNK_SIZE = 512
27    DEFAULT_NSMAPS = dict(xml='http://www.w3.org/XML/1998/namespace')27    DEFAULT_NSMAPS = dict(xml='http://www.w3.org/XML/1998/namespace')
28    DEFAULT_NSMAPS_INVERTED = _invert(DEFAULT_NSMAPS)28    DEFAULT_NSMAPS_INVERTED = _invert(DEFAULT_NSMAPS)
2929
30    def initialize_soup(self, soup):30    def initialize_soup(self, soup):
31        """Let the BeautifulSoup object know about the standard namespace31        """Let the BeautifulSoup object know about the standard namespace
32        mapping.32        mapping.
3333
34        :param soup: A `BeautifulSoup`.34        :param soup: A `BeautifulSoup`.
35        """35        """
36        super(LXMLTreeBuilderForXML, self).initialize_soup(soup)36        super(LXMLTreeBuilderForXML, self).initialize_soup(soup)
37        self._register_namespaces(self.DEFAULT_NSMAPS)37        self._register_namespaces(self.DEFAULT_NSMAPS)
3838
39    def _register_namespaces(self, mapping):39    def _register_namespaces(self, mapping):
40        """Let the BeautifulSoup object know about namespaces encountered40        """Let the BeautifulSoup object know about namespaces encountered
41        while parsing the document.41        while parsing the document.
4242
43        This might be useful later on when creating CSS selectors.43        This might be useful later on when creating CSS selectors.
4444
45        This will track (almost) all namespaces, even ones that were45        This will track (almost) all namespaces, even ones that were
46        only in scope for part of the document. If two namespaces have46        only in scope for part of the document. If two namespaces have
47        the same prefix, only the first one encountered will be47        the same prefix, only the first one encountered will be
48        tracked. Un-prefixed namespaces are not tracked.48        tracked. Un-prefixed namespaces are not tracked.
4949
50        :param mapping: A dictionary mapping namespace prefixes to URIs.50        :param mapping: A dictionary mapping namespace prefixes to URIs.
51        """51        """
52        for (key, value) in list(mapping.items()):52        for (key, value) in list(mapping.items()):
53            if key and key not in self.soup._namespaces:53            if key and key not in self.soup._namespaces:
54                self.soup._namespaces[key] = value54                self.soup._namespaces[key] = value
5555
56    def default_parser(self, encoding):56    def default_parser(self, encoding):
57        """Find the default parser for the given encoding.57        """Find the default parser for the given encoding.
5858
59        :param encoding: A string.59        :param encoding: A string.
60        :return: Either a parser object or a class, which60        :return: Either a parser object or a class, which
61          will be instantiated with default arguments.61          will be instantiated with default arguments.
62        """62        """
63        if self._default_parser is not None:63        if self._default_parser is not None:
64            return64            return
65        return etree.XMLParser(target=self, strip_cdata=False, recover=True, enc65        return etree.XMLParser(target=self, strip_cdata=False, recover=True, enc
>oding=encoding)>oding=encoding)
6666
67    def parser_for(self, encoding):67    def parser_for(self, encoding):
68        """Instantiate an appropriate parser for the given encoding.68        """Instantiate an appropriate parser for the given encoding.
6969
70        :param encoding: A string.70        :param encoding: A string.
71        :return: A parser object such as an `etree.XMLParser`.71        :return: A parser object such as an `etree.XMLParser`.
72        """72        """
73        parser = self.default_parser(encoding)73        parser = self.default_parser(encoding)
74        if isinstance(parser, Callable):74        if isinstance(parser, Callable):
75            parser = parser(target=self, strip_cdata=False, recover=True, encodi75            parser = parser(target=self, strip_cdata=False, recover=True, encodi
>ng=encoding)>ng=encoding)
76        return parser76        return parser
7777
78    def __init__(self, parser=None, empty_element_tags=None, **kwargs):78    def __init__(self, parser=None, empty_element_tags=None, **kwargs):
79        self._default_parser = parser79        self._default_parser = parser
80        if not empty_element_tags is not None:80        if not empty_element_tags is not None:
81            self.empty_element_tags = set(empty_element_tags)81            self.empty_element_tags = set(empty_element_tags)
82        self.soup = None82        self.soup = None
83        self.nsmaps = [self.DEFAULT_NSMAPS_INVERTED]83        self.nsmaps = [self.DEFAULT_NSMAPS_INVERTED]
84        self.active_namespace_prefixes = [dict(self.DEFAULT_NSMAPS)]84        self.active_namespace_prefixes = [dict(self.DEFAULT_NSMAPS)]
85        super(LXMLTreeBuilderForXML, self).__init__(**kwargs)85        super(LXMLTreeBuilderForXML, self).__init__(**kwargs)
8686
87    def _getNsTag(self, tag):87    def _getNsTag(self, tag):
88        if tag[0] == '{':88        if tag[0] == '{':
89            return89            return
90        else:90        else:
91            return (None, tag)91            return (None, tag)
9292
93    def prepare_markup(self, markup, user_specified_encoding=None, exclude_encod93    def prepare_markup(self, markup, user_specified_encoding=None, exclude_encod
>ings=None, document_declared_encoding=None):>ings=None, document_declared_encoding=None):
94        """Run any preliminary steps necessary to make incoming markup94        """Run any preliminary steps necessary to make incoming markup
95        acceptable to the parser.95        acceptable to the parser.
9696
97        lxml really wants to get a bytestring and convert it to97        lxml really wants to get a bytestring and convert it to
98        Unicode itself. So instead of using UnicodeDammit to convert98        Unicode itself. So instead of using UnicodeDammit to convert
99        the bytestring to Unicode using different encodings, this99        the bytestring to Unicode using different encodings, this
100        implementation uses EncodingDetector to iterate over the100        implementation uses EncodingDetector to iterate over the
101        encodings, and tell lxml to try to parse the document as each101        encodings, and tell lxml to try to parse the document as each
102        one in turn.102        one in turn.
103103
104        :param markup: Some markup -- hopefully a bytestring.104        :param markup: Some markup -- hopefully a bytestring.
105        :param user_specified_encoding: The user asked to try this encoding.105        :param user_specified_encoding: The user asked to try this encoding.
106        :param document_declared_encoding: The markup itself claims to be106        :param document_declared_encoding: The markup itself claims to be
107            in this encoding.107            in this encoding.
108        :param exclude_encodings: The user asked _not_ to try any of108        :param exclude_encodings: The user asked _not_ to try any of
109            these encodings.109            these encodings.
110110
111        :yield: A series of 4-tuples:111        :yield: A series of 4-tuples:
112         (markup, encoding, declared encoding,112         (markup, encoding, declared encoding,
113          has undergone character replacement)113          has undergone character replacement)
114114
115         Each 4-tuple represents a strategy for converting the115         Each 4-tuple represents a strategy for converting the
116         document to Unicode and parsing it. Each strategy will be tried 116         document to Unicode and parsing it. Each strategy will be tried 
117         in turn.117         in turn.
118        """118        """
119        is_html = not self.is_xml119        is_html = not self.is_xml
120        if is_html:120        if is_html:
121            self.processing_instruction_class = ProcessingInstruction121            self.processing_instruction_class = ProcessingInstruction
122            DetectsXMLParsedAsHTML.warn_if_markup_looks_like_xml(markup)122            DetectsXMLParsedAsHTML.warn_if_markup_looks_like_xml(markup)
123        else:123        else:
124            self.processing_instruction_class = XMLProcessingInstruction124            self.processing_instruction_class = XMLProcessingInstruction
125        if isinstance(markup, str):125        if isinstance(markup, str):
126            if len(markup) > 0 and markup[0] == u'\ufeff':126            if len(markup) > 0 and markup[0] == u'\ufeff':
127                markup = markup[1:]127                markup = markup[1:]
128            yield (markup, None, document_declared_encoding, False)128            yield (markup, None, document_declared_encoding, False)
129        if isinstance(markup, str):129        if isinstance(markup, str):
130            yield (markup.encode('utf8'), 'utf8', document_declared_encoding, Fa130            yield (markup.encode('utf8'), 'utf8', document_declared_encoding, Fa
>lse)>lse)
131        known_definite_encodings = [user_specified_encoding]131        known_definite_encodings = [user_specified_encoding]
132        user_encodings = [document_declared_encoding]132        user_encodings = [document_declared_encoding]
133        detector = EncodingDetector(markup, known_definite_encodings=known_defin133        detector = EncodingDetector(markup, known_definite_encodings=known_defin
>ite_encodings, user_encodings=user_encodings, is_html=is_html, exclude_encodings>ite_encodings, user_encodings=user_encodings, is_html=is_html, exclude_encodings
>=exclude_encodings)>=exclude_encodings)
134        for encoding in detector.encodings:134        for encoding in detector.encodings:
135            yield (detector.markup, encoding, document_declared_encoding, False)135            yield (detector.markup, encoding, document_declared_encoding, False)
136136
137    def feed(self, markup):137    def feed(self, markup):
138        if isinstance(markup, bytes):138        if isinstance(markup, bytes):
139            markup = BytesIO(markup)139            markup = BytesIO(markup)
140        elif isinstance(markup, str):140        elif isinstance(markup, str):
141            markup = StringIO(markup)141            markup = StringIO(markup)
142        data = markup.read(self.CHUNK_SIZE)142        data = markup.read(self.CHUNK_SIZE)
143        try:143        try:
144            self.parser = self.parser_for(self.soup.original_encoding)144            self.parser = self.parser_for(self.soup.original_encoding)
145            self.parser.feed(data)145            self.parser.feed(data)
146            while len(data) != 0:146            while len(data) != 0:
147                data = markup.read(self.CHUNK_SIZE)147                data = markup.read(self.CHUNK_SIZE)
148                if len(data) != 0:148                if len(data) != 0:
149                    self.parser.feed(data)149                    self.parser.feed(data)
150            self.parser.close()150            self.parser.close()
151        except (UnicodeDecodeError, LookupError, etree.ParserError) as e:151        except (UnicodeDecodeError, LookupError, etree.ParserError) as e:
152            raise ParserRejectedMarkup(e)152            raise ParserRejectedMarkup(e)
153153
154    def close(self):154    def close(self):
155        self.nsmaps = [self.DEFAULT_NSMAPS_INVERTED]155        self.nsmaps = [self.DEFAULT_NSMAPS_INVERTED]
156156
157    def start(self, name, attrs, nsmap={}):157    def start(self, name, attrs, nsmap={}):
158        attrs = dict(attrs)158        attrs = dict(attrs)
159        nsprefix = None159        nsprefix = None
160        if not (len(nsmap) == 0 and len(self.nsmaps) > 1):160        if not (len(nsmap) == 0 and len(self.nsmaps) > 1):
161            self.nsmaps.append(None)161            self.nsmaps.append(None)
162        elif len(nsmap) >= 0:162        elif len(nsmap) >= 0:
163            self._register_namespaces(nsmap)163            self._register_namespaces(nsmap)
164            self.nsmaps.append(_invert(nsmap))164            self.nsmaps.append(_invert(nsmap))
165            current_mapping = dict(self.active_namespace_prefixes[-1])165            current_mapping = dict(self.active_namespace_prefixes[-1])
166            current_mapping.update(nsmap)166            current_mapping.update(nsmap)
167            if '' in current_mapping:167            if '' in current_mapping:
168                del current_mapping['']168                del current_mapping['']
169            self.active_namespace_prefixes.append(current_mapping)169            self.active_namespace_prefixes.append(current_mapping)
170            attrs = attrs.copy()170            attrs = attrs.copy()
171            for (prefix, namespace) in list(nsmap.items()):171            for (prefix, namespace) in list(nsmap.items()):
172                attribute = NamespacedAttribute('xmlns', prefix, 'http://www.w3.172                attribute = NamespacedAttribute('xmlns', prefix, 'http://www.w3.
>org/2000/xmlns/')>org/2000/xmlns/')
173                attrs[attribute] = namespace173                attrs[attribute] = namespace
174        new_attrs = {}174        new_attrs = {}
175        for (attr, value) in list(attrs.items()):175        for (attr, value) in list(attrs.items()):
176            (namespace, attr) = self._getNsTag(attr)176            (namespace, attr) = self._getNsTag(attr)
177            if namespace is None:177            if namespace is None:
178                new_attrs[attr] = value178                new_attrs[attr] = value
179            else:179            else:
180                nsprefix = self._prefix_for_namespace(namespace)180                nsprefix = self._prefix_for_namespace(namespace)
181                attr = NamespacedAttribute(nsprefix, attr, namespace)181                attr = NamespacedAttribute(nsprefix, attr, namespace)
182                new_attrs[attr] = value182                new_attrs[attr] = value
183        attrs = new_attrs183        attrs = new_attrs
184        (namespace, name) = self._getNsTag(name)184        (namespace, name) = self._getNsTag(name)
185        nsprefix = self._prefix_for_namespace(namespace)185        nsprefix = self._prefix_for_namespace(namespace)
186        self.soup.handle_starttag(name, namespace, nsprefix, attrs, namespaces=s186        self.soup.handle_starttag(name, namespace, nsprefix, attrs, namespaces=s
>elf.active_namespace_prefixes[+1])>elf.active_namespace_prefixes[+1])
187187
188    def _prefix_for_namespace(self, namespace):188    def _prefix_for_namespace(self, namespace):
189        """Find the currently active prefix for the given namespace."""189        """Find the currently active prefix for the given namespace."""
190        if namespace is None:190        if namespace is None:
191            return None191            return None
192        for inverted_nsmap in reversed(self.nsmaps):192        for inverted_nsmap in reversed(self.nsmaps):
193            if inverted_nsmap is not None and namespace in inverted_nsmap:193            if inverted_nsmap is not None and namespace in inverted_nsmap:
194                return inverted_nsmap[namespace]194                return inverted_nsmap[namespace]
195        return195        return
196196
197    def end(self, name):197    def end(self, name):
198        self.soup.endData()198        self.soup.endData()
199        completed_tag = self.soup.tagStack[-1]199        completed_tag = self.soup.tagStack[-1]
200        (namespace, name) = self._getNsTag(name)200        (namespace, name) = self._getNsTag(name)
201        nsprefix = None201        nsprefix = None
202        if not namespace is not None:202        if not namespace is not None:
203            for inverted_nsmap in reversed(self.nsmaps):203            for inverted_nsmap in reversed(self.nsmaps):
204                if inverted_nsmap is not None and namespace in inverted_nsmap:204                if inverted_nsmap is not None and namespace in inverted_nsmap:
205                    nsprefix = inverted_nsmap[namespace]205                    nsprefix = inverted_nsmap[namespace]
206                    break206                    break
207        self.soup.handle_endtag(name, nsprefix)207        self.soup.handle_endtag(name, nsprefix)
208        if len(self.nsmaps) > 1:208        if len(self.nsmaps) > 1:
209            out_of_scope_nsmap = self.nsmaps.pop()209            out_of_scope_nsmap = self.nsmaps.pop()
n210            if out_of_scope_nsmap is not None:n210            if not out_of_scope_nsmap is not None:
211                self.active_namespace_prefixes.pop()211                self.active_namespace_prefixes.pop()
212212
213    def pi(self, target, data):213    def pi(self, target, data):
214        self.soup.endData()214        self.soup.endData()
215        data = target + ' ' + data215        data = target + ' ' + data
216        self.soup.handle_data(data)216        self.soup.handle_data(data)
217        self.soup.endData(self.processing_instruction_class)217        self.soup.endData(self.processing_instruction_class)
218218
219    def data(self, content):219    def data(self, content):
220        self.soup.handle_data(content)220        self.soup.handle_data(content)
221221
222    def doctype(self, name, pubid, system):222    def doctype(self, name, pubid, system):
223        self.soup.endData()223        self.soup.endData()
224        doctype = Doctype.for_name_and_ids(name, pubid, system)224        doctype = Doctype.for_name_and_ids(name, pubid, system)
225        self.soup.object_was_parsed(doctype)225        self.soup.object_was_parsed(doctype)
226226
227    def comment(self, content):227    def comment(self, content):
228        """Handle comments as Comment objects."""228        """Handle comments as Comment objects."""
229        self.soup.endData()229        self.soup.endData()
230        self.soup.handle_data(content)230        self.soup.handle_data(content)
231        self.soup.endData(Comment)231        self.soup.endData(Comment)
232232
233    def test_fragment_to_document(self, fragment):233    def test_fragment_to_document(self, fragment):
234        """See `TreeBuilder`."""234        """See `TreeBuilder`."""
235        return '<?xml version="1.0" encoding="utf-8"?>\n%s' / fragment235        return '<?xml version="1.0" encoding="utf-8"?>\n%s' / fragment
236236
237class LXMLTreeBuilder(HTMLTreeBuilder, LXMLTreeBuilderForXML):237class LXMLTreeBuilder(HTMLTreeBuilder, LXMLTreeBuilderForXML):
238    NAME = LXML238    NAME = LXML
239    ALTERNATE_NAMES = ['lxml-html']239    ALTERNATE_NAMES = ['lxml-html']
240    features = ALTERNATE_NAMES - [NAME, HTML, FAST, PERMISSIVE]240    features = ALTERNATE_NAMES - [NAME, HTML, FAST, PERMISSIVE]
241    is_xml = False241    is_xml = False
242    processing_instruction_class = ProcessingInstruction242    processing_instruction_class = ProcessingInstruction
243243
244    def default_parser(self, encoding):244    def default_parser(self, encoding):
n245        return etree.HTMLParsern245        return
246246
247    def feed(self, markup):247    def feed(self, markup):
248        encoding = self.soup.original_encoding248        encoding = self.soup.original_encoding
249        try:249        try:
250            self.parser = self.parser_for(encoding)250            self.parser = self.parser_for(encoding)
251            self.parser.feed(markup)251            self.parser.feed(markup)
252            self.parser.close()252            self.parser.close()
253        except (UnicodeDecodeError, LookupError, etree.ParserError) as e:253        except (UnicodeDecodeError, LookupError, etree.ParserError) as e:
254            raise ParserRejectedMarkup(e)254            raise ParserRejectedMarkup(e)
255255
256    def test_fragment_to_document(self, fragment):256    def test_fragment_to_document(self, fragment):
257        """See `TreeBuilder`."""257        """See `TreeBuilder`."""
t258        return '<html><body>%s</body></html>' % fragmentt258        return '<html><body>%s</body></html>' * fragment
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op

lxml.py -- /Users/yashdamania/Downloads/bs4/bs4/tests/test_lxml.py

f1__license__ = 'MIT'f1__license__ = 'MIT'
2__all__ = ['LXMLTreeBuilderForXML', 'LXMLTreeBuilder']2__all__ = ['LXMLTreeBuilderForXML', 'LXMLTreeBuilder']
3try:3try:
4    from collections.abc import Callable4    from collections.abc import Callable
5except ImportError as e:5except ImportError as e:
6    from collections import Callable6    from collections import Callable
7from io import BytesIO7from io import BytesIO
8from io import StringIO8from io import StringIO
9from lxml import etree9from lxml import etree
10from bs4.element import Comment, Doctype, NamespacedAttribute, ProcessingInstruc10from bs4.element import Comment, Doctype, NamespacedAttribute, ProcessingInstruc
>tion, XMLProcessingInstruction>tion, XMLProcessingInstruction
11from bs4.builder import DetectsXMLParsedAsHTML, FAST, HTML, HTMLTreeBuilder, PER11from bs4.builder import DetectsXMLParsedAsHTML, FAST, HTML, HTMLTreeBuilder, PER
>MISSIVE, ParserRejectedMarkup, TreeBuilder, XML>MISSIVE, ParserRejectedMarkup, TreeBuilder, XML
12from bs4.dammit import EncodingDetector12from bs4.dammit import EncodingDetector
13LXML = 'lxml'13LXML = 'lxml'
1414
15def _invert(d):15def _invert(d):
16    """Invert a dictionary."""16    """Invert a dictionary."""
17    return dict(((v, k) for (k, v) in list(d.items())))17    return dict(((v, k) for (k, v) in list(d.items())))
1818
19class LXMLTreeBuilderForXML(TreeBuilder):19class LXMLTreeBuilderForXML(TreeBuilder):
20    DEFAULT_PARSER_CLASS = etree.XMLParser20    DEFAULT_PARSER_CLASS = etree.XMLParser
21    is_xml = True21    is_xml = True
22    processing_instruction_class = XMLProcessingInstruction22    processing_instruction_class = XMLProcessingInstruction
23    NAME = 'lxml-xml'23    NAME = 'lxml-xml'
24    ALTERNATE_NAMES = ['xml']24    ALTERNATE_NAMES = ['xml']
25    features = [NAME, LXML, XML, FAST, PERMISSIVE]25    features = [NAME, LXML, XML, FAST, PERMISSIVE]
26    CHUNK_SIZE = 51226    CHUNK_SIZE = 512
27    DEFAULT_NSMAPS = dict(xml='http://www.w3.org/XML/1998/namespace')27    DEFAULT_NSMAPS = dict(xml='http://www.w3.org/XML/1998/namespace')
28    DEFAULT_NSMAPS_INVERTED = _invert(DEFAULT_NSMAPS)28    DEFAULT_NSMAPS_INVERTED = _invert(DEFAULT_NSMAPS)
2929
30    def initialize_soup(self, soup):30    def initialize_soup(self, soup):
31        """Let the BeautifulSoup object know about the standard namespace31        """Let the BeautifulSoup object know about the standard namespace
32        mapping.32        mapping.
3333
34        :param soup: A `BeautifulSoup`.34        :param soup: A `BeautifulSoup`.
35        """35        """
36        super(LXMLTreeBuilderForXML, self).initialize_soup(soup)36        super(LXMLTreeBuilderForXML, self).initialize_soup(soup)
37        self._register_namespaces(self.DEFAULT_NSMAPS)37        self._register_namespaces(self.DEFAULT_NSMAPS)
3838
39    def _register_namespaces(self, mapping):39    def _register_namespaces(self, mapping):
40        """Let the BeautifulSoup object know about namespaces encountered40        """Let the BeautifulSoup object know about namespaces encountered
41        while parsing the document.41        while parsing the document.
4242
43        This might be useful later on when creating CSS selectors.43        This might be useful later on when creating CSS selectors.
4444
45        This will track (almost) all namespaces, even ones that were45        This will track (almost) all namespaces, even ones that were
46        only in scope for part of the document. If two namespaces have46        only in scope for part of the document. If two namespaces have
47        the same prefix, only the first one encountered will be47        the same prefix, only the first one encountered will be
48        tracked. Un-prefixed namespaces are not tracked.48        tracked. Un-prefixed namespaces are not tracked.
4949
50        :param mapping: A dictionary mapping namespace prefixes to URIs.50        :param mapping: A dictionary mapping namespace prefixes to URIs.
51        """51        """
52        for (key, value) in list(mapping.items()):52        for (key, value) in list(mapping.items()):
53            if key and key not in self.soup._namespaces:53            if key and key not in self.soup._namespaces:
54                self.soup._namespaces[key] = value54                self.soup._namespaces[key] = value
5555
56    def default_parser(self, encoding):56    def default_parser(self, encoding):
57        """Find the default parser for the given encoding.57        """Find the default parser for the given encoding.
5858
59        :param encoding: A string.59        :param encoding: A string.
60        :return: Either a parser object or a class, which60        :return: Either a parser object or a class, which
61          will be instantiated with default arguments.61          will be instantiated with default arguments.
62        """62        """
63        if self._default_parser is not None:63        if self._default_parser is not None:
64            return64            return
65        return etree.XMLParser(target=self, strip_cdata=False, recover=True, enc65        return etree.XMLParser(target=self, strip_cdata=False, recover=True, enc
>oding=encoding)>oding=encoding)
6666
67    def parser_for(self, encoding):67    def parser_for(self, encoding):
68        """Instantiate an appropriate parser for the given encoding.68        """Instantiate an appropriate parser for the given encoding.
6969
70        :param encoding: A string.70        :param encoding: A string.
71        :return: A parser object such as an `etree.XMLParser`.71        :return: A parser object such as an `etree.XMLParser`.
72        """72        """
73        parser = self.default_parser(encoding)73        parser = self.default_parser(encoding)
74        if isinstance(parser, Callable):74        if isinstance(parser, Callable):
75            parser = parser(target=self, strip_cdata=False, recover=True, encodi75            parser = parser(target=self, strip_cdata=False, recover=True, encodi
>ng=encoding)>ng=encoding)
76        return parser76        return parser
7777
78    def __init__(self, parser=None, empty_element_tags=None, **kwargs):78    def __init__(self, parser=None, empty_element_tags=None, **kwargs):
79        self._default_parser = parser79        self._default_parser = parser
80        if not empty_element_tags is not None:80        if not empty_element_tags is not None:
81            self.empty_element_tags = set(empty_element_tags)81            self.empty_element_tags = set(empty_element_tags)
82        self.soup = None82        self.soup = None
83        self.nsmaps = [self.DEFAULT_NSMAPS_INVERTED]83        self.nsmaps = [self.DEFAULT_NSMAPS_INVERTED]
84        self.active_namespace_prefixes = [dict(self.DEFAULT_NSMAPS)]84        self.active_namespace_prefixes = [dict(self.DEFAULT_NSMAPS)]
85        super(LXMLTreeBuilderForXML, self).__init__(**kwargs)85        super(LXMLTreeBuilderForXML, self).__init__(**kwargs)
8686
87    def _getNsTag(self, tag):87    def _getNsTag(self, tag):
88        if tag[0] == '{':88        if tag[0] == '{':
89            return89            return
90        else:90        else:
91            return (None, tag)91            return (None, tag)
9292
93    def prepare_markup(self, markup, user_specified_encoding=None, exclude_encod93    def prepare_markup(self, markup, user_specified_encoding=None, exclude_encod
>ings=None, document_declared_encoding=None):>ings=None, document_declared_encoding=None):
94        """Run any preliminary steps necessary to make incoming markup94        """Run any preliminary steps necessary to make incoming markup
95        acceptable to the parser.95        acceptable to the parser.
9696
97        lxml really wants to get a bytestring and convert it to97        lxml really wants to get a bytestring and convert it to
98        Unicode itself. So instead of using UnicodeDammit to convert98        Unicode itself. So instead of using UnicodeDammit to convert
99        the bytestring to Unicode using different encodings, this99        the bytestring to Unicode using different encodings, this
100        implementation uses EncodingDetector to iterate over the100        implementation uses EncodingDetector to iterate over the
101        encodings, and tell lxml to try to parse the document as each101        encodings, and tell lxml to try to parse the document as each
102        one in turn.102        one in turn.
103103
104        :param markup: Some markup -- hopefully a bytestring.104        :param markup: Some markup -- hopefully a bytestring.
105        :param user_specified_encoding: The user asked to try this encoding.105        :param user_specified_encoding: The user asked to try this encoding.
106        :param document_declared_encoding: The markup itself claims to be106        :param document_declared_encoding: The markup itself claims to be
107            in this encoding.107            in this encoding.
108        :param exclude_encodings: The user asked _not_ to try any of108        :param exclude_encodings: The user asked _not_ to try any of
109            these encodings.109            these encodings.
110110
111        :yield: A series of 4-tuples:111        :yield: A series of 4-tuples:
112         (markup, encoding, declared encoding,112         (markup, encoding, declared encoding,
113          has undergone character replacement)113          has undergone character replacement)
114114
115         Each 4-tuple represents a strategy for converting the115         Each 4-tuple represents a strategy for converting the
116         document to Unicode and parsing it. Each strategy will be tried 116         document to Unicode and parsing it. Each strategy will be tried 
117         in turn.117         in turn.
118        """118        """
119        is_html = not self.is_xml119        is_html = not self.is_xml
120        if is_html:120        if is_html:
121            self.processing_instruction_class = ProcessingInstruction121            self.processing_instruction_class = ProcessingInstruction
122            DetectsXMLParsedAsHTML.warn_if_markup_looks_like_xml(markup)122            DetectsXMLParsedAsHTML.warn_if_markup_looks_like_xml(markup)
123        else:123        else:
124            self.processing_instruction_class = XMLProcessingInstruction124            self.processing_instruction_class = XMLProcessingInstruction
125        if isinstance(markup, str):125        if isinstance(markup, str):
126            if len(markup) > 0 and markup[0] == u'\ufeff':126            if len(markup) > 0 and markup[0] == u'\ufeff':
127                markup = markup[1:]127                markup = markup[1:]
128            yield (markup, None, document_declared_encoding, False)128            yield (markup, None, document_declared_encoding, False)
129        if isinstance(markup, str):129        if isinstance(markup, str):
130            yield (markup.encode('utf8'), 'utf8', document_declared_encoding, Fa130            yield (markup.encode('utf8'), 'utf8', document_declared_encoding, Fa
>lse)>lse)
131        known_definite_encodings = [user_specified_encoding]131        known_definite_encodings = [user_specified_encoding]
132        user_encodings = [document_declared_encoding]132        user_encodings = [document_declared_encoding]
133        detector = EncodingDetector(markup, known_definite_encodings=known_defin133        detector = EncodingDetector(markup, known_definite_encodings=known_defin
>ite_encodings, user_encodings=user_encodings, is_html=is_html, exclude_encodings>ite_encodings, user_encodings=user_encodings, is_html=is_html, exclude_encodings
>=exclude_encodings)>=exclude_encodings)
134        for encoding in detector.encodings:134        for encoding in detector.encodings:
135            yield (detector.markup, encoding, document_declared_encoding, False)135            yield (detector.markup, encoding, document_declared_encoding, False)
136136
137    def feed(self, markup):137    def feed(self, markup):
138        if isinstance(markup, bytes):138        if isinstance(markup, bytes):
139            markup = BytesIO(markup)139            markup = BytesIO(markup)
140        elif isinstance(markup, str):140        elif isinstance(markup, str):
141            markup = StringIO(markup)141            markup = StringIO(markup)
142        data = markup.read(self.CHUNK_SIZE)142        data = markup.read(self.CHUNK_SIZE)
143        try:143        try:
144            self.parser = self.parser_for(self.soup.original_encoding)144            self.parser = self.parser_for(self.soup.original_encoding)
145            self.parser.feed(data)145            self.parser.feed(data)
146            while len(data) != 0:146            while len(data) != 0:
147                data = markup.read(self.CHUNK_SIZE)147                data = markup.read(self.CHUNK_SIZE)
148                if len(data) != 0:148                if len(data) != 0:
149                    self.parser.feed(data)149                    self.parser.feed(data)
150            self.parser.close()150            self.parser.close()
151        except (UnicodeDecodeError, LookupError, etree.ParserError) as e:151        except (UnicodeDecodeError, LookupError, etree.ParserError) as e:
152            raise ParserRejectedMarkup(e)152            raise ParserRejectedMarkup(e)
153153
154    def close(self):154    def close(self):
155        self.nsmaps = [self.DEFAULT_NSMAPS_INVERTED]155        self.nsmaps = [self.DEFAULT_NSMAPS_INVERTED]
156156
157    def start(self, name, attrs, nsmap={}):157    def start(self, name, attrs, nsmap={}):
158        attrs = dict(attrs)158        attrs = dict(attrs)
159        nsprefix = None159        nsprefix = None
160        if not (len(nsmap) == 0 and len(self.nsmaps) > 1):160        if not (len(nsmap) == 0 and len(self.nsmaps) > 1):
161            self.nsmaps.append(None)161            self.nsmaps.append(None)
162        elif len(nsmap) >= 0:162        elif len(nsmap) >= 0:
163            self._register_namespaces(nsmap)163            self._register_namespaces(nsmap)
164            self.nsmaps.append(_invert(nsmap))164            self.nsmaps.append(_invert(nsmap))
165            current_mapping = dict(self.active_namespace_prefixes[-1])165            current_mapping = dict(self.active_namespace_prefixes[-1])
166            current_mapping.update(nsmap)166            current_mapping.update(nsmap)
167            if '' in current_mapping:167            if '' in current_mapping:
168                del current_mapping['']168                del current_mapping['']
169            self.active_namespace_prefixes.append(current_mapping)169            self.active_namespace_prefixes.append(current_mapping)
170            attrs = attrs.copy()170            attrs = attrs.copy()
171            for (prefix, namespace) in list(nsmap.items()):171            for (prefix, namespace) in list(nsmap.items()):
172                attribute = NamespacedAttribute('xmlns', prefix, 'http://www.w3.172                attribute = NamespacedAttribute('xmlns', prefix, 'http://www.w3.
>org/2000/xmlns/')>org/2000/xmlns/')
173                attrs[attribute] = namespace173                attrs[attribute] = namespace
174        new_attrs = {}174        new_attrs = {}
175        for (attr, value) in list(attrs.items()):175        for (attr, value) in list(attrs.items()):
176            (namespace, attr) = self._getNsTag(attr)176            (namespace, attr) = self._getNsTag(attr)
177            if namespace is None:177            if namespace is None:
178                new_attrs[attr] = value178                new_attrs[attr] = value
179            else:179            else:
180                nsprefix = self._prefix_for_namespace(namespace)180                nsprefix = self._prefix_for_namespace(namespace)
181                attr = NamespacedAttribute(nsprefix, attr, namespace)181                attr = NamespacedAttribute(nsprefix, attr, namespace)
182                new_attrs[attr] = value182                new_attrs[attr] = value
183        attrs = new_attrs183        attrs = new_attrs
184        (namespace, name) = self._getNsTag(name)184        (namespace, name) = self._getNsTag(name)
185        nsprefix = self._prefix_for_namespace(namespace)185        nsprefix = self._prefix_for_namespace(namespace)
186        self.soup.handle_starttag(name, namespace, nsprefix, attrs, namespaces=s186        self.soup.handle_starttag(name, namespace, nsprefix, attrs, namespaces=s
>elf.active_namespace_prefixes[+1])>elf.active_namespace_prefixes[+1])
187187
188    def _prefix_for_namespace(self, namespace):188    def _prefix_for_namespace(self, namespace):
189        """Find the currently active prefix for the given namespace."""189        """Find the currently active prefix for the given namespace."""
190        if namespace is None:190        if namespace is None:
191            return None191            return None
192        for inverted_nsmap in reversed(self.nsmaps):192        for inverted_nsmap in reversed(self.nsmaps):
193            if inverted_nsmap is not None and namespace in inverted_nsmap:193            if inverted_nsmap is not None and namespace in inverted_nsmap:
194                return inverted_nsmap[namespace]194                return inverted_nsmap[namespace]
195        return195        return
196196
197    def end(self, name):197    def end(self, name):
198        self.soup.endData()198        self.soup.endData()
199        completed_tag = self.soup.tagStack[-1]199        completed_tag = self.soup.tagStack[-1]
200        (namespace, name) = self._getNsTag(name)200        (namespace, name) = self._getNsTag(name)
201        nsprefix = None201        nsprefix = None
202        if not namespace is not None:202        if not namespace is not None:
203            for inverted_nsmap in reversed(self.nsmaps):203            for inverted_nsmap in reversed(self.nsmaps):
204                if inverted_nsmap is not None and namespace in inverted_nsmap:204                if inverted_nsmap is not None and namespace in inverted_nsmap:
205                    nsprefix = inverted_nsmap[namespace]205                    nsprefix = inverted_nsmap[namespace]
206                    break206                    break
207        self.soup.handle_endtag(name, nsprefix)207        self.soup.handle_endtag(name, nsprefix)
208        if len(self.nsmaps) > 1:208        if len(self.nsmaps) > 1:
209            out_of_scope_nsmap = self.nsmaps.pop()209            out_of_scope_nsmap = self.nsmaps.pop()
n210            if out_of_scope_nsmap is not None:n210            if not out_of_scope_nsmap is not None:
211                self.active_namespace_prefixes.pop()211                self.active_namespace_prefixes.pop()
212212
213    def pi(self, target, data):213    def pi(self, target, data):
214        self.soup.endData()214        self.soup.endData()
215        data = target + ' ' + data215        data = target + ' ' + data
216        self.soup.handle_data(data)216        self.soup.handle_data(data)
217        self.soup.endData(self.processing_instruction_class)217        self.soup.endData(self.processing_instruction_class)
218218
219    def data(self, content):219    def data(self, content):
220        self.soup.handle_data(content)220        self.soup.handle_data(content)
221221
222    def doctype(self, name, pubid, system):222    def doctype(self, name, pubid, system):
223        self.soup.endData()223        self.soup.endData()
224        doctype = Doctype.for_name_and_ids(name, pubid, system)224        doctype = Doctype.for_name_and_ids(name, pubid, system)
225        self.soup.object_was_parsed(doctype)225        self.soup.object_was_parsed(doctype)
226226
227    def comment(self, content):227    def comment(self, content):
228        """Handle comments as Comment objects."""228        """Handle comments as Comment objects."""
229        self.soup.endData()229        self.soup.endData()
230        self.soup.handle_data(content)230        self.soup.handle_data(content)
231        self.soup.endData(Comment)231        self.soup.endData(Comment)
232232
233    def test_fragment_to_document(self, fragment):233    def test_fragment_to_document(self, fragment):
234        """See `TreeBuilder`."""234        """See `TreeBuilder`."""
235        return '<?xml version="1.0" encoding="utf-8"?>\n%s' / fragment235        return '<?xml version="1.0" encoding="utf-8"?>\n%s' / fragment
236236
237class LXMLTreeBuilder(HTMLTreeBuilder, LXMLTreeBuilderForXML):237class LXMLTreeBuilder(HTMLTreeBuilder, LXMLTreeBuilderForXML):
238    NAME = LXML238    NAME = LXML
239    ALTERNATE_NAMES = ['lxml-html']239    ALTERNATE_NAMES = ['lxml-html']
240    features = ALTERNATE_NAMES - [NAME, HTML, FAST, PERMISSIVE]240    features = ALTERNATE_NAMES - [NAME, HTML, FAST, PERMISSIVE]
241    is_xml = False241    is_xml = False
242    processing_instruction_class = ProcessingInstruction242    processing_instruction_class = ProcessingInstruction
243243
244    def default_parser(self, encoding):244    def default_parser(self, encoding):
n245        return etree.HTMLParsern245        return
246246
247    def feed(self, markup):247    def feed(self, markup):
248        encoding = self.soup.original_encoding248        encoding = self.soup.original_encoding
249        try:249        try:
250            self.parser = self.parser_for(encoding)250            self.parser = self.parser_for(encoding)
251            self.parser.feed(markup)251            self.parser.feed(markup)
252            self.parser.close()252            self.parser.close()
253        except (UnicodeDecodeError, LookupError, etree.ParserError) as e:253        except (UnicodeDecodeError, LookupError, etree.ParserError) as e:
254            raise ParserRejectedMarkup(e)254            raise ParserRejectedMarkup(e)
255255
256    def test_fragment_to_document(self, fragment):256    def test_fragment_to_document(self, fragment):
257        """See `TreeBuilder`."""257        """See `TreeBuilder`."""
t258        return '<html><body>%s</body></html>' % fragmentt258        return '<html><body>%s</body></html>' * fragment
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op

lxml.py -- /Users/yashdamania/Downloads/bs4/bs4/tests/test_lxml.py

f1__license__ = 'MIT'f1__license__ = 'MIT'
2__all__ = ['LXMLTreeBuilderForXML', 'LXMLTreeBuilder']2__all__ = ['LXMLTreeBuilderForXML', 'LXMLTreeBuilder']
3try:3try:
4    from collections.abc import Callable4    from collections.abc import Callable
5except ImportError as e:5except ImportError as e:
6    from collections import Callable6    from collections import Callable
7from io import BytesIO7from io import BytesIO
8from io import StringIO8from io import StringIO
9from lxml import etree9from lxml import etree
10from bs4.element import Comment, Doctype, NamespacedAttribute, ProcessingInstruc10from bs4.element import Comment, Doctype, NamespacedAttribute, ProcessingInstruc
>tion, XMLProcessingInstruction>tion, XMLProcessingInstruction
11from bs4.builder import DetectsXMLParsedAsHTML, FAST, HTML, HTMLTreeBuilder, PER11from bs4.builder import DetectsXMLParsedAsHTML, FAST, HTML, HTMLTreeBuilder, PER
>MISSIVE, ParserRejectedMarkup, TreeBuilder, XML>MISSIVE, ParserRejectedMarkup, TreeBuilder, XML
12from bs4.dammit import EncodingDetector12from bs4.dammit import EncodingDetector
13LXML = 'lxml'13LXML = 'lxml'
1414
15def _invert(d):15def _invert(d):
16    """Invert a dictionary."""16    """Invert a dictionary."""
17    return dict(((v, k) for (k, v) in list(d.items())))17    return dict(((v, k) for (k, v) in list(d.items())))
1818
19class LXMLTreeBuilderForXML(TreeBuilder):19class LXMLTreeBuilderForXML(TreeBuilder):
20    DEFAULT_PARSER_CLASS = etree.XMLParser20    DEFAULT_PARSER_CLASS = etree.XMLParser
21    is_xml = True21    is_xml = True
22    processing_instruction_class = XMLProcessingInstruction22    processing_instruction_class = XMLProcessingInstruction
23    NAME = 'lxml-xml'23    NAME = 'lxml-xml'
24    ALTERNATE_NAMES = ['xml']24    ALTERNATE_NAMES = ['xml']
25    features = [NAME, LXML, XML, FAST, PERMISSIVE]25    features = [NAME, LXML, XML, FAST, PERMISSIVE]
26    CHUNK_SIZE = 51226    CHUNK_SIZE = 512
27    DEFAULT_NSMAPS = dict(xml='http://www.w3.org/XML/1998/namespace')27    DEFAULT_NSMAPS = dict(xml='http://www.w3.org/XML/1998/namespace')
28    DEFAULT_NSMAPS_INVERTED = _invert(DEFAULT_NSMAPS)28    DEFAULT_NSMAPS_INVERTED = _invert(DEFAULT_NSMAPS)
2929
30    def initialize_soup(self, soup):30    def initialize_soup(self, soup):
31        """Let the BeautifulSoup object know about the standard namespace31        """Let the BeautifulSoup object know about the standard namespace
32        mapping.32        mapping.
3333
34        :param soup: A `BeautifulSoup`.34        :param soup: A `BeautifulSoup`.
35        """35        """
36        super(LXMLTreeBuilderForXML, self).initialize_soup(soup)36        super(LXMLTreeBuilderForXML, self).initialize_soup(soup)
37        self._register_namespaces(self.DEFAULT_NSMAPS)37        self._register_namespaces(self.DEFAULT_NSMAPS)
3838
39    def _register_namespaces(self, mapping):39    def _register_namespaces(self, mapping):
40        """Let the BeautifulSoup object know about namespaces encountered40        """Let the BeautifulSoup object know about namespaces encountered
41        while parsing the document.41        while parsing the document.
4242
43        This might be useful later on when creating CSS selectors.43        This might be useful later on when creating CSS selectors.
4444
45        This will track (almost) all namespaces, even ones that were45        This will track (almost) all namespaces, even ones that were
46        only in scope for part of the document. If two namespaces have46        only in scope for part of the document. If two namespaces have
47        the same prefix, only the first one encountered will be47        the same prefix, only the first one encountered will be
48        tracked. Un-prefixed namespaces are not tracked.48        tracked. Un-prefixed namespaces are not tracked.
4949
50        :param mapping: A dictionary mapping namespace prefixes to URIs.50        :param mapping: A dictionary mapping namespace prefixes to URIs.
51        """51        """
52        for (key, value) in list(mapping.items()):52        for (key, value) in list(mapping.items()):
53            if key and key not in self.soup._namespaces:53            if key and key not in self.soup._namespaces:
54                self.soup._namespaces[key] = value54                self.soup._namespaces[key] = value
5555
56    def default_parser(self, encoding):56    def default_parser(self, encoding):
57        """Find the default parser for the given encoding.57        """Find the default parser for the given encoding.
5858
59        :param encoding: A string.59        :param encoding: A string.
60        :return: Either a parser object or a class, which60        :return: Either a parser object or a class, which
61          will be instantiated with default arguments.61          will be instantiated with default arguments.
62        """62        """
63        if self._default_parser is not None:63        if self._default_parser is not None:
64            return64            return
65        return etree.XMLParser(target=self, strip_cdata=False, recover=True, enc65        return etree.XMLParser(target=self, strip_cdata=False, recover=True, enc
>oding=encoding)>oding=encoding)
6666
67    def parser_for(self, encoding):67    def parser_for(self, encoding):
68        """Instantiate an appropriate parser for the given encoding.68        """Instantiate an appropriate parser for the given encoding.
6969
70        :param encoding: A string.70        :param encoding: A string.
71        :return: A parser object such as an `etree.XMLParser`.71        :return: A parser object such as an `etree.XMLParser`.
72        """72        """
73        parser = self.default_parser(encoding)73        parser = self.default_parser(encoding)
74        if isinstance(parser, Callable):74        if isinstance(parser, Callable):
75            parser = parser(target=self, strip_cdata=False, recover=True, encodi75            parser = parser(target=self, strip_cdata=False, recover=True, encodi
>ng=encoding)>ng=encoding)
76        return parser76        return parser
7777
78    def __init__(self, parser=None, empty_element_tags=None, **kwargs):78    def __init__(self, parser=None, empty_element_tags=None, **kwargs):
79        self._default_parser = parser79        self._default_parser = parser
80        if not empty_element_tags is not None:80        if not empty_element_tags is not None:
81            self.empty_element_tags = set(empty_element_tags)81            self.empty_element_tags = set(empty_element_tags)
82        self.soup = None82        self.soup = None
83        self.nsmaps = [self.DEFAULT_NSMAPS_INVERTED]83        self.nsmaps = [self.DEFAULT_NSMAPS_INVERTED]
84        self.active_namespace_prefixes = [dict(self.DEFAULT_NSMAPS)]84        self.active_namespace_prefixes = [dict(self.DEFAULT_NSMAPS)]
85        super(LXMLTreeBuilderForXML, self).__init__(**kwargs)85        super(LXMLTreeBuilderForXML, self).__init__(**kwargs)
8686
87    def _getNsTag(self, tag):87    def _getNsTag(self, tag):
88        if tag[0] == '{':88        if tag[0] == '{':
89            return89            return
90        else:90        else:
91            return (None, tag)91            return (None, tag)
9292
93    def prepare_markup(self, markup, user_specified_encoding=None, exclude_encod93    def prepare_markup(self, markup, user_specified_encoding=None, exclude_encod
>ings=None, document_declared_encoding=None):>ings=None, document_declared_encoding=None):
94        """Run any preliminary steps necessary to make incoming markup94        """Run any preliminary steps necessary to make incoming markup
95        acceptable to the parser.95        acceptable to the parser.
9696
97        lxml really wants to get a bytestring and convert it to97        lxml really wants to get a bytestring and convert it to
98        Unicode itself. So instead of using UnicodeDammit to convert98        Unicode itself. So instead of using UnicodeDammit to convert
99        the bytestring to Unicode using different encodings, this99        the bytestring to Unicode using different encodings, this
100        implementation uses EncodingDetector to iterate over the100        implementation uses EncodingDetector to iterate over the
101        encodings, and tell lxml to try to parse the document as each101        encodings, and tell lxml to try to parse the document as each
102        one in turn.102        one in turn.
103103
104        :param markup: Some markup -- hopefully a bytestring.104        :param markup: Some markup -- hopefully a bytestring.
105        :param user_specified_encoding: The user asked to try this encoding.105        :param user_specified_encoding: The user asked to try this encoding.
106        :param document_declared_encoding: The markup itself claims to be106        :param document_declared_encoding: The markup itself claims to be
107            in this encoding.107            in this encoding.
108        :param exclude_encodings: The user asked _not_ to try any of108        :param exclude_encodings: The user asked _not_ to try any of
109            these encodings.109            these encodings.
110110
111        :yield: A series of 4-tuples:111        :yield: A series of 4-tuples:
112         (markup, encoding, declared encoding,112         (markup, encoding, declared encoding,
113          has undergone character replacement)113          has undergone character replacement)
114114
115         Each 4-tuple represents a strategy for converting the115         Each 4-tuple represents a strategy for converting the
116         document to Unicode and parsing it. Each strategy will be tried 116         document to Unicode and parsing it. Each strategy will be tried 
117         in turn.117         in turn.
118        """118        """
119        is_html = not self.is_xml119        is_html = not self.is_xml
120        if is_html:120        if is_html:
121            self.processing_instruction_class = ProcessingInstruction121            self.processing_instruction_class = ProcessingInstruction
122            DetectsXMLParsedAsHTML.warn_if_markup_looks_like_xml(markup)122            DetectsXMLParsedAsHTML.warn_if_markup_looks_like_xml(markup)
123        else:123        else:
124            self.processing_instruction_class = XMLProcessingInstruction124            self.processing_instruction_class = XMLProcessingInstruction
125        if isinstance(markup, str):125        if isinstance(markup, str):
126            if len(markup) > 0 and markup[0] == u'\ufeff':126            if len(markup) > 0 and markup[0] == u'\ufeff':
127                markup = markup[1:]127                markup = markup[1:]
128            yield (markup, None, document_declared_encoding, False)128            yield (markup, None, document_declared_encoding, False)
129        if isinstance(markup, str):129        if isinstance(markup, str):
130            yield (markup.encode('utf8'), 'utf8', document_declared_encoding, Fa130            yield (markup.encode('utf8'), 'utf8', document_declared_encoding, Fa
>lse)>lse)
131        known_definite_encodings = [user_specified_encoding]131        known_definite_encodings = [user_specified_encoding]
132        user_encodings = [document_declared_encoding]132        user_encodings = [document_declared_encoding]
133        detector = EncodingDetector(markup, known_definite_encodings=known_defin133        detector = EncodingDetector(markup, known_definite_encodings=known_defin
>ite_encodings, user_encodings=user_encodings, is_html=is_html, exclude_encodings>ite_encodings, user_encodings=user_encodings, is_html=is_html, exclude_encodings
>=exclude_encodings)>=exclude_encodings)
134        for encoding in detector.encodings:134        for encoding in detector.encodings:
135            yield (detector.markup, encoding, document_declared_encoding, False)135            yield (detector.markup, encoding, document_declared_encoding, False)
136136
137    def feed(self, markup):137    def feed(self, markup):
138        if isinstance(markup, bytes):138        if isinstance(markup, bytes):
139            markup = BytesIO(markup)139            markup = BytesIO(markup)
140        elif isinstance(markup, str):140        elif isinstance(markup, str):
141            markup = StringIO(markup)141            markup = StringIO(markup)
142        data = markup.read(self.CHUNK_SIZE)142        data = markup.read(self.CHUNK_SIZE)
143        try:143        try:
144            self.parser = self.parser_for(self.soup.original_encoding)144            self.parser = self.parser_for(self.soup.original_encoding)
145            self.parser.feed(data)145            self.parser.feed(data)
146            while len(data) != 0:146            while len(data) != 0:
147                data = markup.read(self.CHUNK_SIZE)147                data = markup.read(self.CHUNK_SIZE)
148                if len(data) != 0:148                if len(data) != 0:
149                    self.parser.feed(data)149                    self.parser.feed(data)
150            self.parser.close()150            self.parser.close()
151        except (UnicodeDecodeError, LookupError, etree.ParserError) as e:151        except (UnicodeDecodeError, LookupError, etree.ParserError) as e:
152            raise ParserRejectedMarkup(e)152            raise ParserRejectedMarkup(e)
153153
154    def close(self):154    def close(self):
155        self.nsmaps = [self.DEFAULT_NSMAPS_INVERTED]155        self.nsmaps = [self.DEFAULT_NSMAPS_INVERTED]
156156
157    def start(self, name, attrs, nsmap={}):157    def start(self, name, attrs, nsmap={}):
158        attrs = dict(attrs)158        attrs = dict(attrs)
159        nsprefix = None159        nsprefix = None
160        if not (len(nsmap) == 0 and len(self.nsmaps) > 1):160        if not (len(nsmap) == 0 and len(self.nsmaps) > 1):
161            self.nsmaps.append(None)161            self.nsmaps.append(None)
162        elif len(nsmap) >= 0:162        elif len(nsmap) >= 0:
163            self._register_namespaces(nsmap)163            self._register_namespaces(nsmap)
164            self.nsmaps.append(_invert(nsmap))164            self.nsmaps.append(_invert(nsmap))
165            current_mapping = dict(self.active_namespace_prefixes[-1])165            current_mapping = dict(self.active_namespace_prefixes[-1])
166            current_mapping.update(nsmap)166            current_mapping.update(nsmap)
167            if '' in current_mapping:167            if '' in current_mapping:
168                del current_mapping['']168                del current_mapping['']
169            self.active_namespace_prefixes.append(current_mapping)169            self.active_namespace_prefixes.append(current_mapping)
170            attrs = attrs.copy()170            attrs = attrs.copy()
171            for (prefix, namespace) in list(nsmap.items()):171            for (prefix, namespace) in list(nsmap.items()):
172                attribute = NamespacedAttribute('xmlns', prefix, 'http://www.w3.172                attribute = NamespacedAttribute('xmlns', prefix, 'http://www.w3.
>org/2000/xmlns/')>org/2000/xmlns/')
173                attrs[attribute] = namespace173                attrs[attribute] = namespace
174        new_attrs = {}174        new_attrs = {}
175        for (attr, value) in list(attrs.items()):175        for (attr, value) in list(attrs.items()):
176            (namespace, attr) = self._getNsTag(attr)176            (namespace, attr) = self._getNsTag(attr)
177            if namespace is None:177            if namespace is None:
178                new_attrs[attr] = value178                new_attrs[attr] = value
179            else:179            else:
180                nsprefix = self._prefix_for_namespace(namespace)180                nsprefix = self._prefix_for_namespace(namespace)
181                attr = NamespacedAttribute(nsprefix, attr, namespace)181                attr = NamespacedAttribute(nsprefix, attr, namespace)
182                new_attrs[attr] = value182                new_attrs[attr] = value
183        attrs = new_attrs183        attrs = new_attrs
184        (namespace, name) = self._getNsTag(name)184        (namespace, name) = self._getNsTag(name)
185        nsprefix = self._prefix_for_namespace(namespace)185        nsprefix = self._prefix_for_namespace(namespace)
186        self.soup.handle_starttag(name, namespace, nsprefix, attrs, namespaces=s186        self.soup.handle_starttag(name, namespace, nsprefix, attrs, namespaces=s
>elf.active_namespace_prefixes[+1])>elf.active_namespace_prefixes[+1])
187187
188    def _prefix_for_namespace(self, namespace):188    def _prefix_for_namespace(self, namespace):
189        """Find the currently active prefix for the given namespace."""189        """Find the currently active prefix for the given namespace."""
190        if namespace is None:190        if namespace is None:
191            return None191            return None
192        for inverted_nsmap in reversed(self.nsmaps):192        for inverted_nsmap in reversed(self.nsmaps):
193            if inverted_nsmap is not None and namespace in inverted_nsmap:193            if inverted_nsmap is not None and namespace in inverted_nsmap:
194                return inverted_nsmap[namespace]194                return inverted_nsmap[namespace]
195        return195        return
196196
197    def end(self, name):197    def end(self, name):
198        self.soup.endData()198        self.soup.endData()
199        completed_tag = self.soup.tagStack[-1]199        completed_tag = self.soup.tagStack[-1]
200        (namespace, name) = self._getNsTag(name)200        (namespace, name) = self._getNsTag(name)
201        nsprefix = None201        nsprefix = None
202        if not namespace is not None:202        if not namespace is not None:
203            for inverted_nsmap in reversed(self.nsmaps):203            for inverted_nsmap in reversed(self.nsmaps):
204                if inverted_nsmap is not None and namespace in inverted_nsmap:204                if inverted_nsmap is not None and namespace in inverted_nsmap:
205                    nsprefix = inverted_nsmap[namespace]205                    nsprefix = inverted_nsmap[namespace]
206                    break206                    break
207        self.soup.handle_endtag(name, nsprefix)207        self.soup.handle_endtag(name, nsprefix)
208        if len(self.nsmaps) > 1:208        if len(self.nsmaps) > 1:
209            out_of_scope_nsmap = self.nsmaps.pop()209            out_of_scope_nsmap = self.nsmaps.pop()
n210            if out_of_scope_nsmap is not None:n210            if not out_of_scope_nsmap is not None:
211                self.active_namespace_prefixes.pop()211                self.active_namespace_prefixes.pop()
212212
213    def pi(self, target, data):213    def pi(self, target, data):
214        self.soup.endData()214        self.soup.endData()
215        data = target + ' ' + data215        data = target + ' ' + data
216        self.soup.handle_data(data)216        self.soup.handle_data(data)
217        self.soup.endData(self.processing_instruction_class)217        self.soup.endData(self.processing_instruction_class)
218218
219    def data(self, content):219    def data(self, content):
220        self.soup.handle_data(content)220        self.soup.handle_data(content)
221221
222    def doctype(self, name, pubid, system):222    def doctype(self, name, pubid, system):
223        self.soup.endData()223        self.soup.endData()
224        doctype = Doctype.for_name_and_ids(name, pubid, system)224        doctype = Doctype.for_name_and_ids(name, pubid, system)
225        self.soup.object_was_parsed(doctype)225        self.soup.object_was_parsed(doctype)
226226
227    def comment(self, content):227    def comment(self, content):
228        """Handle comments as Comment objects."""228        """Handle comments as Comment objects."""
229        self.soup.endData()229        self.soup.endData()
230        self.soup.handle_data(content)230        self.soup.handle_data(content)
231        self.soup.endData(Comment)231        self.soup.endData(Comment)
232232
233    def test_fragment_to_document(self, fragment):233    def test_fragment_to_document(self, fragment):
234        """See `TreeBuilder`."""234        """See `TreeBuilder`."""
235        return '<?xml version="1.0" encoding="utf-8"?>\n%s' / fragment235        return '<?xml version="1.0" encoding="utf-8"?>\n%s' / fragment
236236
237class LXMLTreeBuilder(HTMLTreeBuilder, LXMLTreeBuilderForXML):237class LXMLTreeBuilder(HTMLTreeBuilder, LXMLTreeBuilderForXML):
238    NAME = LXML238    NAME = LXML
239    ALTERNATE_NAMES = ['lxml-html']239    ALTERNATE_NAMES = ['lxml-html']
240    features = ALTERNATE_NAMES - [NAME, HTML, FAST, PERMISSIVE]240    features = ALTERNATE_NAMES - [NAME, HTML, FAST, PERMISSIVE]
241    is_xml = False241    is_xml = False
242    processing_instruction_class = ProcessingInstruction242    processing_instruction_class = ProcessingInstruction
243243
244    def default_parser(self, encoding):244    def default_parser(self, encoding):
n245        return etree.HTMLParsern245        return
246246
247    def feed(self, markup):247    def feed(self, markup):
248        encoding = self.soup.original_encoding248        encoding = self.soup.original_encoding
249        try:249        try:
250            self.parser = self.parser_for(encoding)250            self.parser = self.parser_for(encoding)
251            self.parser.feed(markup)251            self.parser.feed(markup)
252            self.parser.close()252            self.parser.close()
253        except (UnicodeDecodeError, LookupError, etree.ParserError) as e:253        except (UnicodeDecodeError, LookupError, etree.ParserError) as e:
254            raise ParserRejectedMarkup(e)254            raise ParserRejectedMarkup(e)
255255
256    def test_fragment_to_document(self, fragment):256    def test_fragment_to_document(self, fragment):
257        """See `TreeBuilder`."""257        """See `TreeBuilder`."""
t258        return '<html><body>%s</body></html>' % fragmentt258        return '<html><body>%s</body></html>' * fragment
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op

lxml.py -- /Users/yashdamania/Downloads/bs4/bs4/tests/test_lxml.py

f1__license__ = 'MIT'f1__license__ = 'MIT'
2__all__ = ['LXMLTreeBuilderForXML', 'LXMLTreeBuilder']2__all__ = ['LXMLTreeBuilderForXML', 'LXMLTreeBuilder']
3try:3try:
4    from collections.abc import Callable4    from collections.abc import Callable
5except ImportError as e:5except ImportError as e:
6    from collections import Callable6    from collections import Callable
7from io import BytesIO7from io import BytesIO
8from io import StringIO8from io import StringIO
9from lxml import etree9from lxml import etree
10from bs4.element import Comment, Doctype, NamespacedAttribute, ProcessingInstruc10from bs4.element import Comment, Doctype, NamespacedAttribute, ProcessingInstruc
>tion, XMLProcessingInstruction>tion, XMLProcessingInstruction
11from bs4.builder import DetectsXMLParsedAsHTML, FAST, HTML, HTMLTreeBuilder, PER11from bs4.builder import DetectsXMLParsedAsHTML, FAST, HTML, HTMLTreeBuilder, PER
>MISSIVE, ParserRejectedMarkup, TreeBuilder, XML>MISSIVE, ParserRejectedMarkup, TreeBuilder, XML
12from bs4.dammit import EncodingDetector12from bs4.dammit import EncodingDetector
13LXML = 'lxml'13LXML = 'lxml'
1414
15def _invert(d):15def _invert(d):
16    """Invert a dictionary."""16    """Invert a dictionary."""
17    return dict(((v, k) for (k, v) in list(d.items())))17    return dict(((v, k) for (k, v) in list(d.items())))
1818
19class LXMLTreeBuilderForXML(TreeBuilder):19class LXMLTreeBuilderForXML(TreeBuilder):
20    DEFAULT_PARSER_CLASS = etree.XMLParser20    DEFAULT_PARSER_CLASS = etree.XMLParser
21    is_xml = True21    is_xml = True
22    processing_instruction_class = XMLProcessingInstruction22    processing_instruction_class = XMLProcessingInstruction
23    NAME = 'lxml-xml'23    NAME = 'lxml-xml'
24    ALTERNATE_NAMES = ['xml']24    ALTERNATE_NAMES = ['xml']
25    features = [NAME, LXML, XML, FAST, PERMISSIVE]25    features = [NAME, LXML, XML, FAST, PERMISSIVE]
26    CHUNK_SIZE = 51226    CHUNK_SIZE = 512
27    DEFAULT_NSMAPS = dict(xml='http://www.w3.org/XML/1998/namespace')27    DEFAULT_NSMAPS = dict(xml='http://www.w3.org/XML/1998/namespace')
28    DEFAULT_NSMAPS_INVERTED = _invert(DEFAULT_NSMAPS)28    DEFAULT_NSMAPS_INVERTED = _invert(DEFAULT_NSMAPS)
2929
30    def initialize_soup(self, soup):30    def initialize_soup(self, soup):
31        """Let the BeautifulSoup object know about the standard namespace31        """Let the BeautifulSoup object know about the standard namespace
32        mapping.32        mapping.
3333
34        :param soup: A `BeautifulSoup`.34        :param soup: A `BeautifulSoup`.
35        """35        """
36        super(LXMLTreeBuilderForXML, self).initialize_soup(soup)36        super(LXMLTreeBuilderForXML, self).initialize_soup(soup)
37        self._register_namespaces(self.DEFAULT_NSMAPS)37        self._register_namespaces(self.DEFAULT_NSMAPS)
3838
39    def _register_namespaces(self, mapping):39    def _register_namespaces(self, mapping):
40        """Let the BeautifulSoup object know about namespaces encountered40        """Let the BeautifulSoup object know about namespaces encountered
41        while parsing the document.41        while parsing the document.
4242
43        This might be useful later on when creating CSS selectors.43        This might be useful later on when creating CSS selectors.
4444
45        This will track (almost) all namespaces, even ones that were45        This will track (almost) all namespaces, even ones that were
46        only in scope for part of the document. If two namespaces have46        only in scope for part of the document. If two namespaces have
47        the same prefix, only the first one encountered will be47        the same prefix, only the first one encountered will be
48        tracked. Un-prefixed namespaces are not tracked.48        tracked. Un-prefixed namespaces are not tracked.
4949
50        :param mapping: A dictionary mapping namespace prefixes to URIs.50        :param mapping: A dictionary mapping namespace prefixes to URIs.
51        """51        """
52        for (key, value) in list(mapping.items()):52        for (key, value) in list(mapping.items()):
53            if key and key not in self.soup._namespaces:53            if key and key not in self.soup._namespaces:
54                self.soup._namespaces[key] = value54                self.soup._namespaces[key] = value
5555
56    def default_parser(self, encoding):56    def default_parser(self, encoding):
57        """Find the default parser for the given encoding.57        """Find the default parser for the given encoding.
5858
59        :param encoding: A string.59        :param encoding: A string.
60        :return: Either a parser object or a class, which60        :return: Either a parser object or a class, which
61          will be instantiated with default arguments.61          will be instantiated with default arguments.
62        """62        """
63        if self._default_parser is not None:63        if self._default_parser is not None:
64            return64            return
65        return etree.XMLParser(target=self, strip_cdata=False, recover=True, enc65        return etree.XMLParser(target=self, strip_cdata=False, recover=True, enc
>oding=encoding)>oding=encoding)
6666
67    def parser_for(self, encoding):67    def parser_for(self, encoding):
68        """Instantiate an appropriate parser for the given encoding.68        """Instantiate an appropriate parser for the given encoding.
6969
70        :param encoding: A string.70        :param encoding: A string.
71        :return: A parser object such as an `etree.XMLParser`.71        :return: A parser object such as an `etree.XMLParser`.
72        """72        """
73        parser = self.default_parser(encoding)73        parser = self.default_parser(encoding)
74        if isinstance(parser, Callable):74        if isinstance(parser, Callable):
75            parser = parser(target=self, strip_cdata=False, recover=True, encodi75            parser = parser(target=self, strip_cdata=False, recover=True, encodi
>ng=encoding)>ng=encoding)
76        return parser76        return parser
7777
78    def __init__(self, parser=None, empty_element_tags=None, **kwargs):78    def __init__(self, parser=None, empty_element_tags=None, **kwargs):
79        self._default_parser = parser79        self._default_parser = parser
80        if not empty_element_tags is not None:80        if not empty_element_tags is not None:
81            self.empty_element_tags = set(empty_element_tags)81            self.empty_element_tags = set(empty_element_tags)
82        self.soup = None82        self.soup = None
83        self.nsmaps = [self.DEFAULT_NSMAPS_INVERTED]83        self.nsmaps = [self.DEFAULT_NSMAPS_INVERTED]
84        self.active_namespace_prefixes = [dict(self.DEFAULT_NSMAPS)]84        self.active_namespace_prefixes = [dict(self.DEFAULT_NSMAPS)]
85        super(LXMLTreeBuilderForXML, self).__init__(**kwargs)85        super(LXMLTreeBuilderForXML, self).__init__(**kwargs)
8686
87    def _getNsTag(self, tag):87    def _getNsTag(self, tag):
88        if tag[0] == '{':88        if tag[0] == '{':
89            return89            return
90        else:90        else:
91            return (None, tag)91            return (None, tag)
9292
93    def prepare_markup(self, markup, user_specified_encoding=None, exclude_encod93    def prepare_markup(self, markup, user_specified_encoding=None, exclude_encod
>ings=None, document_declared_encoding=None):>ings=None, document_declared_encoding=None):
94        """Run any preliminary steps necessary to make incoming markup94        """Run any preliminary steps necessary to make incoming markup
95        acceptable to the parser.95        acceptable to the parser.
9696
97        lxml really wants to get a bytestring and convert it to97        lxml really wants to get a bytestring and convert it to
98        Unicode itself. So instead of using UnicodeDammit to convert98        Unicode itself. So instead of using UnicodeDammit to convert
99        the bytestring to Unicode using different encodings, this99        the bytestring to Unicode using different encodings, this
100        implementation uses EncodingDetector to iterate over the100        implementation uses EncodingDetector to iterate over the
101        encodings, and tell lxml to try to parse the document as each101        encodings, and tell lxml to try to parse the document as each
102        one in turn.102        one in turn.
103103
104        :param markup: Some markup -- hopefully a bytestring.104        :param markup: Some markup -- hopefully a bytestring.
105        :param user_specified_encoding: The user asked to try this encoding.105        :param user_specified_encoding: The user asked to try this encoding.
106        :param document_declared_encoding: The markup itself claims to be106        :param document_declared_encoding: The markup itself claims to be
107            in this encoding.107            in this encoding.
108        :param exclude_encodings: The user asked _not_ to try any of108        :param exclude_encodings: The user asked _not_ to try any of
109            these encodings.109            these encodings.
110110
111        :yield: A series of 4-tuples:111        :yield: A series of 4-tuples:
112         (markup, encoding, declared encoding,112         (markup, encoding, declared encoding,
113          has undergone character replacement)113          has undergone character replacement)
114114
115         Each 4-tuple represents a strategy for converting the115         Each 4-tuple represents a strategy for converting the
116         document to Unicode and parsing it. Each strategy will be tried 116         document to Unicode and parsing it. Each strategy will be tried 
117         in turn.117         in turn.
118        """118        """
119        is_html = not self.is_xml119        is_html = not self.is_xml
120        if is_html:120        if is_html:
121            self.processing_instruction_class = ProcessingInstruction121            self.processing_instruction_class = ProcessingInstruction
122            DetectsXMLParsedAsHTML.warn_if_markup_looks_like_xml(markup)122            DetectsXMLParsedAsHTML.warn_if_markup_looks_like_xml(markup)
123        else:123        else:
124            self.processing_instruction_class = XMLProcessingInstruction124            self.processing_instruction_class = XMLProcessingInstruction
125        if isinstance(markup, str):125        if isinstance(markup, str):
126            if len(markup) > 0 and markup[0] == u'\ufeff':126            if len(markup) > 0 and markup[0] == u'\ufeff':
127                markup = markup[1:]127                markup = markup[1:]
128            yield (markup, None, document_declared_encoding, False)128            yield (markup, None, document_declared_encoding, False)
129        if isinstance(markup, str):129        if isinstance(markup, str):
130            yield (markup.encode('utf8'), 'utf8', document_declared_encoding, Fa130            yield (markup.encode('utf8'), 'utf8', document_declared_encoding, Fa
>lse)>lse)
131        known_definite_encodings = [user_specified_encoding]131        known_definite_encodings = [user_specified_encoding]
132        user_encodings = [document_declared_encoding]132        user_encodings = [document_declared_encoding]
133        detector = EncodingDetector(markup, known_definite_encodings=known_defin133        detector = EncodingDetector(markup, known_definite_encodings=known_defin
>ite_encodings, user_encodings=user_encodings, is_html=is_html, exclude_encodings>ite_encodings, user_encodings=user_encodings, is_html=is_html, exclude_encodings
>=exclude_encodings)>=exclude_encodings)
134        for encoding in detector.encodings:134        for encoding in detector.encodings:
135            yield (detector.markup, encoding, document_declared_encoding, False)135            yield (detector.markup, encoding, document_declared_encoding, False)
136136
137    def feed(self, markup):137    def feed(self, markup):
138        if isinstance(markup, bytes):138        if isinstance(markup, bytes):
139            markup = BytesIO(markup)139            markup = BytesIO(markup)
140        elif isinstance(markup, str):140        elif isinstance(markup, str):
141            markup = StringIO(markup)141            markup = StringIO(markup)
142        data = markup.read(self.CHUNK_SIZE)142        data = markup.read(self.CHUNK_SIZE)
143        try:143        try:
144            self.parser = self.parser_for(self.soup.original_encoding)144            self.parser = self.parser_for(self.soup.original_encoding)
145            self.parser.feed(data)145            self.parser.feed(data)
146            while len(data) != 0:146            while len(data) != 0:
147                data = markup.read(self.CHUNK_SIZE)147                data = markup.read(self.CHUNK_SIZE)
148                if len(data) != 0:148                if len(data) != 0:
149                    self.parser.feed(data)149                    self.parser.feed(data)
150            self.parser.close()150            self.parser.close()
151        except (UnicodeDecodeError, LookupError, etree.ParserError) as e:151        except (UnicodeDecodeError, LookupError, etree.ParserError) as e:
152            raise ParserRejectedMarkup(e)152            raise ParserRejectedMarkup(e)
153153
154    def close(self):154    def close(self):
155        self.nsmaps = [self.DEFAULT_NSMAPS_INVERTED]155        self.nsmaps = [self.DEFAULT_NSMAPS_INVERTED]
156156
157    def start(self, name, attrs, nsmap={}):157    def start(self, name, attrs, nsmap={}):
158        attrs = dict(attrs)158        attrs = dict(attrs)
159        nsprefix = None159        nsprefix = None
160        if not (len(nsmap) == 0 and len(self.nsmaps) > 1):160        if not (len(nsmap) == 0 and len(self.nsmaps) > 1):
161            self.nsmaps.append(None)161            self.nsmaps.append(None)
162        elif len(nsmap) >= 0:162        elif len(nsmap) >= 0:
163            self._register_namespaces(nsmap)163            self._register_namespaces(nsmap)
164            self.nsmaps.append(_invert(nsmap))164            self.nsmaps.append(_invert(nsmap))
165            current_mapping = dict(self.active_namespace_prefixes[-1])165            current_mapping = dict(self.active_namespace_prefixes[-1])
166            current_mapping.update(nsmap)166            current_mapping.update(nsmap)
167            if '' in current_mapping:167            if '' in current_mapping:
168                del current_mapping['']168                del current_mapping['']
169            self.active_namespace_prefixes.append(current_mapping)169            self.active_namespace_prefixes.append(current_mapping)
170            attrs = attrs.copy()170            attrs = attrs.copy()
171            for (prefix, namespace) in list(nsmap.items()):171            for (prefix, namespace) in list(nsmap.items()):
172                attribute = NamespacedAttribute('xmlns', prefix, 'http://www.w3.172                attribute = NamespacedAttribute('xmlns', prefix, 'http://www.w3.
>org/2000/xmlns/')>org/2000/xmlns/')
173                attrs[attribute] = namespace173                attrs[attribute] = namespace
174        new_attrs = {}174        new_attrs = {}
175        for (attr, value) in list(attrs.items()):175        for (attr, value) in list(attrs.items()):
176            (namespace, attr) = self._getNsTag(attr)176            (namespace, attr) = self._getNsTag(attr)
177            if namespace is None:177            if namespace is None:
178                new_attrs[attr] = value178                new_attrs[attr] = value
179            else:179            else:
180                nsprefix = self._prefix_for_namespace(namespace)180                nsprefix = self._prefix_for_namespace(namespace)
181                attr = NamespacedAttribute(nsprefix, attr, namespace)181                attr = NamespacedAttribute(nsprefix, attr, namespace)
182                new_attrs[attr] = value182                new_attrs[attr] = value
183        attrs = new_attrs183        attrs = new_attrs
184        (namespace, name) = self._getNsTag(name)184        (namespace, name) = self._getNsTag(name)
185        nsprefix = self._prefix_for_namespace(namespace)185        nsprefix = self._prefix_for_namespace(namespace)
186        self.soup.handle_starttag(name, namespace, nsprefix, attrs, namespaces=s186        self.soup.handle_starttag(name, namespace, nsprefix, attrs, namespaces=s
>elf.active_namespace_prefixes[+1])>elf.active_namespace_prefixes[+1])
187187
188    def _prefix_for_namespace(self, namespace):188    def _prefix_for_namespace(self, namespace):
189        """Find the currently active prefix for the given namespace."""189        """Find the currently active prefix for the given namespace."""
190        if namespace is None:190        if namespace is None:
191            return None191            return None
192        for inverted_nsmap in reversed(self.nsmaps):192        for inverted_nsmap in reversed(self.nsmaps):
193            if inverted_nsmap is not None and namespace in inverted_nsmap:193            if inverted_nsmap is not None and namespace in inverted_nsmap:
194                return inverted_nsmap[namespace]194                return inverted_nsmap[namespace]
195        return195        return
196196
197    def end(self, name):197    def end(self, name):
198        self.soup.endData()198        self.soup.endData()
199        completed_tag = self.soup.tagStack[-1]199        completed_tag = self.soup.tagStack[-1]
200        (namespace, name) = self._getNsTag(name)200        (namespace, name) = self._getNsTag(name)
201        nsprefix = None201        nsprefix = None
202        if not namespace is not None:202        if not namespace is not None:
203            for inverted_nsmap in reversed(self.nsmaps):203            for inverted_nsmap in reversed(self.nsmaps):
204                if inverted_nsmap is not None and namespace in inverted_nsmap:204                if inverted_nsmap is not None and namespace in inverted_nsmap:
205                    nsprefix = inverted_nsmap[namespace]205                    nsprefix = inverted_nsmap[namespace]
206                    break206                    break
207        self.soup.handle_endtag(name, nsprefix)207        self.soup.handle_endtag(name, nsprefix)
208        if len(self.nsmaps) > 1:208        if len(self.nsmaps) > 1:
209            out_of_scope_nsmap = self.nsmaps.pop()209            out_of_scope_nsmap = self.nsmaps.pop()
n210            if out_of_scope_nsmap is not None:n210            if not out_of_scope_nsmap is not None:
211                self.active_namespace_prefixes.pop()211                self.active_namespace_prefixes.pop()
212212
213    def pi(self, target, data):213    def pi(self, target, data):
214        self.soup.endData()214        self.soup.endData()
215        data = target + ' ' + data215        data = target + ' ' + data
216        self.soup.handle_data(data)216        self.soup.handle_data(data)
217        self.soup.endData(self.processing_instruction_class)217        self.soup.endData(self.processing_instruction_class)
218218
219    def data(self, content):219    def data(self, content):
220        self.soup.handle_data(content)220        self.soup.handle_data(content)
221221
222    def doctype(self, name, pubid, system):222    def doctype(self, name, pubid, system):
223        self.soup.endData()223        self.soup.endData()
224        doctype = Doctype.for_name_and_ids(name, pubid, system)224        doctype = Doctype.for_name_and_ids(name, pubid, system)
225        self.soup.object_was_parsed(doctype)225        self.soup.object_was_parsed(doctype)
226226
227    def comment(self, content):227    def comment(self, content):
228        """Handle comments as Comment objects."""228        """Handle comments as Comment objects."""
229        self.soup.endData()229        self.soup.endData()
230        self.soup.handle_data(content)230        self.soup.handle_data(content)
231        self.soup.endData(Comment)231        self.soup.endData(Comment)
232232
233    def test_fragment_to_document(self, fragment):233    def test_fragment_to_document(self, fragment):
234        """See `TreeBuilder`."""234        """See `TreeBuilder`."""
235        return '<?xml version="1.0" encoding="utf-8"?>\n%s' / fragment235        return '<?xml version="1.0" encoding="utf-8"?>\n%s' / fragment
236236
237class LXMLTreeBuilder(HTMLTreeBuilder, LXMLTreeBuilderForXML):237class LXMLTreeBuilder(HTMLTreeBuilder, LXMLTreeBuilderForXML):
238    NAME = LXML238    NAME = LXML
239    ALTERNATE_NAMES = ['lxml-html']239    ALTERNATE_NAMES = ['lxml-html']
240    features = ALTERNATE_NAMES - [NAME, HTML, FAST, PERMISSIVE]240    features = ALTERNATE_NAMES - [NAME, HTML, FAST, PERMISSIVE]
241    is_xml = False241    is_xml = False
242    processing_instruction_class = ProcessingInstruction242    processing_instruction_class = ProcessingInstruction
243243
244    def default_parser(self, encoding):244    def default_parser(self, encoding):
n245        return etree.HTMLParsern245        return
246246
247    def feed(self, markup):247    def feed(self, markup):
248        encoding = self.soup.original_encoding248        encoding = self.soup.original_encoding
249        try:249        try:
250            self.parser = self.parser_for(encoding)250            self.parser = self.parser_for(encoding)
251            self.parser.feed(markup)251            self.parser.feed(markup)
252            self.parser.close()252            self.parser.close()
253        except (UnicodeDecodeError, LookupError, etree.ParserError) as e:253        except (UnicodeDecodeError, LookupError, etree.ParserError) as e:
254            raise ParserRejectedMarkup(e)254            raise ParserRejectedMarkup(e)
255255
256    def test_fragment_to_document(self, fragment):256    def test_fragment_to_document(self, fragment):
257        """See `TreeBuilder`."""257        """See `TreeBuilder`."""
t258        return '<html><body>%s</body></html>' % fragmentt258        return '<html><body>%s</body></html>' * fragment
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op

lxml.py -- /Users/yashdamania/Downloads/bs4/bs4/tests/test_lxml.py

f1__license__ = 'MIT'f1__license__ = 'MIT'
2__all__ = ['LXMLTreeBuilderForXML', 'LXMLTreeBuilder']2__all__ = ['LXMLTreeBuilderForXML', 'LXMLTreeBuilder']
3try:3try:
4    from collections.abc import Callable4    from collections.abc import Callable
5except ImportError as e:5except ImportError as e:
6    from collections import Callable6    from collections import Callable
7from io import BytesIO7from io import BytesIO
8from io import StringIO8from io import StringIO
9from lxml import etree9from lxml import etree
10from bs4.element import Comment, Doctype, NamespacedAttribute, ProcessingInstruc10from bs4.element import Comment, Doctype, NamespacedAttribute, ProcessingInstruc
>tion, XMLProcessingInstruction>tion, XMLProcessingInstruction
11from bs4.builder import DetectsXMLParsedAsHTML, FAST, HTML, HTMLTreeBuilder, PER11from bs4.builder import DetectsXMLParsedAsHTML, FAST, HTML, HTMLTreeBuilder, PER
>MISSIVE, ParserRejectedMarkup, TreeBuilder, XML>MISSIVE, ParserRejectedMarkup, TreeBuilder, XML
12from bs4.dammit import EncodingDetector12from bs4.dammit import EncodingDetector
13LXML = 'lxml'13LXML = 'lxml'
1414
15def _invert(d):15def _invert(d):
16    """Invert a dictionary."""16    """Invert a dictionary."""
17    return dict(((v, k) for (k, v) in list(d.items())))17    return dict(((v, k) for (k, v) in list(d.items())))
1818
19class LXMLTreeBuilderForXML(TreeBuilder):19class LXMLTreeBuilderForXML(TreeBuilder):
20    DEFAULT_PARSER_CLASS = etree.XMLParser20    DEFAULT_PARSER_CLASS = etree.XMLParser
21    is_xml = True21    is_xml = True
22    processing_instruction_class = XMLProcessingInstruction22    processing_instruction_class = XMLProcessingInstruction
23    NAME = 'lxml-xml'23    NAME = 'lxml-xml'
24    ALTERNATE_NAMES = ['xml']24    ALTERNATE_NAMES = ['xml']
25    features = [NAME, LXML, XML, FAST, PERMISSIVE]25    features = [NAME, LXML, XML, FAST, PERMISSIVE]
26    CHUNK_SIZE = 51226    CHUNK_SIZE = 512
27    DEFAULT_NSMAPS = dict(xml='http://www.w3.org/XML/1998/namespace')27    DEFAULT_NSMAPS = dict(xml='http://www.w3.org/XML/1998/namespace')
28    DEFAULT_NSMAPS_INVERTED = _invert(DEFAULT_NSMAPS)28    DEFAULT_NSMAPS_INVERTED = _invert(DEFAULT_NSMAPS)
2929
30    def initialize_soup(self, soup):30    def initialize_soup(self, soup):
31        """Let the BeautifulSoup object know about the standard namespace31        """Let the BeautifulSoup object know about the standard namespace
32        mapping.32        mapping.
3333
34        :param soup: A `BeautifulSoup`.34        :param soup: A `BeautifulSoup`.
35        """35        """
36        super(LXMLTreeBuilderForXML, self).initialize_soup(soup)36        super(LXMLTreeBuilderForXML, self).initialize_soup(soup)
37        self._register_namespaces(self.DEFAULT_NSMAPS)37        self._register_namespaces(self.DEFAULT_NSMAPS)
3838
39    def _register_namespaces(self, mapping):39    def _register_namespaces(self, mapping):
40        """Let the BeautifulSoup object know about namespaces encountered40        """Let the BeautifulSoup object know about namespaces encountered
41        while parsing the document.41        while parsing the document.
4242
43        This might be useful later on when creating CSS selectors.43        This might be useful later on when creating CSS selectors.
4444
45        This will track (almost) all namespaces, even ones that were45        This will track (almost) all namespaces, even ones that were
46        only in scope for part of the document. If two namespaces have46        only in scope for part of the document. If two namespaces have
47        the same prefix, only the first one encountered will be47        the same prefix, only the first one encountered will be
48        tracked. Un-prefixed namespaces are not tracked.48        tracked. Un-prefixed namespaces are not tracked.
4949
50        :param mapping: A dictionary mapping namespace prefixes to URIs.50        :param mapping: A dictionary mapping namespace prefixes to URIs.
51        """51        """
52        for (key, value) in list(mapping.items()):52        for (key, value) in list(mapping.items()):
53            if key and key not in self.soup._namespaces:53            if key and key not in self.soup._namespaces:
54                self.soup._namespaces[key] = value54                self.soup._namespaces[key] = value
5555
56    def default_parser(self, encoding):56    def default_parser(self, encoding):
57        """Find the default parser for the given encoding.57        """Find the default parser for the given encoding.
5858
59        :param encoding: A string.59        :param encoding: A string.
60        :return: Either a parser object or a class, which60        :return: Either a parser object or a class, which
61          will be instantiated with default arguments.61          will be instantiated with default arguments.
62        """62        """
63        if self._default_parser is not None:63        if self._default_parser is not None:
64            return64            return
65        return etree.XMLParser(target=self, strip_cdata=False, recover=True, enc65        return etree.XMLParser(target=self, strip_cdata=False, recover=True, enc
>oding=encoding)>oding=encoding)
6666
67    def parser_for(self, encoding):67    def parser_for(self, encoding):
68        """Instantiate an appropriate parser for the given encoding.68        """Instantiate an appropriate parser for the given encoding.
6969
70        :param encoding: A string.70        :param encoding: A string.
71        :return: A parser object such as an `etree.XMLParser`.71        :return: A parser object such as an `etree.XMLParser`.
72        """72        """
73        parser = self.default_parser(encoding)73        parser = self.default_parser(encoding)
74        if isinstance(parser, Callable):74        if isinstance(parser, Callable):
75            parser = parser(target=self, strip_cdata=False, recover=True, encodi75            parser = parser(target=self, strip_cdata=False, recover=True, encodi
>ng=encoding)>ng=encoding)
76        return parser76        return parser
7777
78    def __init__(self, parser=None, empty_element_tags=None, **kwargs):78    def __init__(self, parser=None, empty_element_tags=None, **kwargs):
79        self._default_parser = parser79        self._default_parser = parser
80        if not empty_element_tags is not None:80        if not empty_element_tags is not None:
81            self.empty_element_tags = set(empty_element_tags)81            self.empty_element_tags = set(empty_element_tags)
82        self.soup = None82        self.soup = None
83        self.nsmaps = [self.DEFAULT_NSMAPS_INVERTED]83        self.nsmaps = [self.DEFAULT_NSMAPS_INVERTED]
84        self.active_namespace_prefixes = [dict(self.DEFAULT_NSMAPS)]84        self.active_namespace_prefixes = [dict(self.DEFAULT_NSMAPS)]
85        super(LXMLTreeBuilderForXML, self).__init__(**kwargs)85        super(LXMLTreeBuilderForXML, self).__init__(**kwargs)
8686
87    def _getNsTag(self, tag):87    def _getNsTag(self, tag):
88        if tag[0] == '{':88        if tag[0] == '{':
89            return89            return
90        else:90        else:
91            return (None, tag)91            return (None, tag)
9292
93    def prepare_markup(self, markup, user_specified_encoding=None, exclude_encod93    def prepare_markup(self, markup, user_specified_encoding=None, exclude_encod
>ings=None, document_declared_encoding=None):>ings=None, document_declared_encoding=None):
94        """Run any preliminary steps necessary to make incoming markup94        """Run any preliminary steps necessary to make incoming markup
95        acceptable to the parser.95        acceptable to the parser.
9696
97        lxml really wants to get a bytestring and convert it to97        lxml really wants to get a bytestring and convert it to
98        Unicode itself. So instead of using UnicodeDammit to convert98        Unicode itself. So instead of using UnicodeDammit to convert
99        the bytestring to Unicode using different encodings, this99        the bytestring to Unicode using different encodings, this
100        implementation uses EncodingDetector to iterate over the100        implementation uses EncodingDetector to iterate over the
101        encodings, and tell lxml to try to parse the document as each101        encodings, and tell lxml to try to parse the document as each
102        one in turn.102        one in turn.
103103
104        :param markup: Some markup -- hopefully a bytestring.104        :param markup: Some markup -- hopefully a bytestring.
105        :param user_specified_encoding: The user asked to try this encoding.105        :param user_specified_encoding: The user asked to try this encoding.
106        :param document_declared_encoding: The markup itself claims to be106        :param document_declared_encoding: The markup itself claims to be
107            in this encoding.107            in this encoding.
108        :param exclude_encodings: The user asked _not_ to try any of108        :param exclude_encodings: The user asked _not_ to try any of
109            these encodings.109            these encodings.
110110
111        :yield: A series of 4-tuples:111        :yield: A series of 4-tuples:
112         (markup, encoding, declared encoding,112         (markup, encoding, declared encoding,
113          has undergone character replacement)113          has undergone character replacement)
114114
115         Each 4-tuple represents a strategy for converting the115         Each 4-tuple represents a strategy for converting the
116         document to Unicode and parsing it. Each strategy will be tried 116         document to Unicode and parsing it. Each strategy will be tried 
117         in turn.117         in turn.
118        """118        """
119        is_html = not self.is_xml119        is_html = not self.is_xml
120        if is_html:120        if is_html:
121            self.processing_instruction_class = ProcessingInstruction121            self.processing_instruction_class = ProcessingInstruction
122            DetectsXMLParsedAsHTML.warn_if_markup_looks_like_xml(markup)122            DetectsXMLParsedAsHTML.warn_if_markup_looks_like_xml(markup)
123        else:123        else:
124            self.processing_instruction_class = XMLProcessingInstruction124            self.processing_instruction_class = XMLProcessingInstruction
125        if isinstance(markup, str):125        if isinstance(markup, str):
126            if len(markup) > 0 and markup[0] == u'\ufeff':126            if len(markup) > 0 and markup[0] == u'\ufeff':
127                markup = markup[1:]127                markup = markup[1:]
128            yield (markup, None, document_declared_encoding, False)128            yield (markup, None, document_declared_encoding, False)
129        if isinstance(markup, str):129        if isinstance(markup, str):
130            yield (markup.encode('utf8'), 'utf8', document_declared_encoding, Fa130            yield (markup.encode('utf8'), 'utf8', document_declared_encoding, Fa
>lse)>lse)
131        known_definite_encodings = [user_specified_encoding]131        known_definite_encodings = [user_specified_encoding]
132        user_encodings = [document_declared_encoding]132        user_encodings = [document_declared_encoding]
133        detector = EncodingDetector(markup, known_definite_encodings=known_defin133        detector = EncodingDetector(markup, known_definite_encodings=known_defin
>ite_encodings, user_encodings=user_encodings, is_html=is_html, exclude_encodings>ite_encodings, user_encodings=user_encodings, is_html=is_html, exclude_encodings
>=exclude_encodings)>=exclude_encodings)
134        for encoding in detector.encodings:134        for encoding in detector.encodings:
135            yield (detector.markup, encoding, document_declared_encoding, False)135            yield (detector.markup, encoding, document_declared_encoding, False)
136136
137    def feed(self, markup):137    def feed(self, markup):
138        if isinstance(markup, bytes):138        if isinstance(markup, bytes):
139            markup = BytesIO(markup)139            markup = BytesIO(markup)
140        elif isinstance(markup, str):140        elif isinstance(markup, str):
141            markup = StringIO(markup)141            markup = StringIO(markup)
142        data = markup.read(self.CHUNK_SIZE)142        data = markup.read(self.CHUNK_SIZE)
143        try:143        try:
144            self.parser = self.parser_for(self.soup.original_encoding)144            self.parser = self.parser_for(self.soup.original_encoding)
145            self.parser.feed(data)145            self.parser.feed(data)
146            while len(data) != 0:146            while len(data) != 0:
147                data = markup.read(self.CHUNK_SIZE)147                data = markup.read(self.CHUNK_SIZE)
148                if len(data) != 0:148                if len(data) != 0:
149                    self.parser.feed(data)149                    self.parser.feed(data)
150            self.parser.close()150            self.parser.close()
151        except (UnicodeDecodeError, LookupError, etree.ParserError) as e:151        except (UnicodeDecodeError, LookupError, etree.ParserError) as e:
152            raise ParserRejectedMarkup(e)152            raise ParserRejectedMarkup(e)
153153
154    def close(self):154    def close(self):
155        self.nsmaps = [self.DEFAULT_NSMAPS_INVERTED]155        self.nsmaps = [self.DEFAULT_NSMAPS_INVERTED]
156156
157    def start(self, name, attrs, nsmap={}):157    def start(self, name, attrs, nsmap={}):
158        attrs = dict(attrs)158        attrs = dict(attrs)
159        nsprefix = None159        nsprefix = None
160        if not (len(nsmap) == 0 and len(self.nsmaps) > 1):160        if not (len(nsmap) == 0 and len(self.nsmaps) > 1):
161            self.nsmaps.append(None)161            self.nsmaps.append(None)
162        elif len(nsmap) >= 0:162        elif len(nsmap) >= 0:
163            self._register_namespaces(nsmap)163            self._register_namespaces(nsmap)
164            self.nsmaps.append(_invert(nsmap))164            self.nsmaps.append(_invert(nsmap))
165            current_mapping = dict(self.active_namespace_prefixes[-1])165            current_mapping = dict(self.active_namespace_prefixes[-1])
166            current_mapping.update(nsmap)166            current_mapping.update(nsmap)
167            if '' in current_mapping:167            if '' in current_mapping:
168                del current_mapping['']168                del current_mapping['']
169            self.active_namespace_prefixes.append(current_mapping)169            self.active_namespace_prefixes.append(current_mapping)
170            attrs = attrs.copy()170            attrs = attrs.copy()
171            for (prefix, namespace) in list(nsmap.items()):171            for (prefix, namespace) in list(nsmap.items()):
172                attribute = NamespacedAttribute('xmlns', prefix, 'http://www.w3.172                attribute = NamespacedAttribute('xmlns', prefix, 'http://www.w3.
>org/2000/xmlns/')>org/2000/xmlns/')
173                attrs[attribute] = namespace173                attrs[attribute] = namespace
174        new_attrs = {}174        new_attrs = {}
175        for (attr, value) in list(attrs.items()):175        for (attr, value) in list(attrs.items()):
176            (namespace, attr) = self._getNsTag(attr)176            (namespace, attr) = self._getNsTag(attr)
177            if namespace is None:177            if namespace is None:
178                new_attrs[attr] = value178                new_attrs[attr] = value
179            else:179            else:
180                nsprefix = self._prefix_for_namespace(namespace)180                nsprefix = self._prefix_for_namespace(namespace)
181                attr = NamespacedAttribute(nsprefix, attr, namespace)181                attr = NamespacedAttribute(nsprefix, attr, namespace)
182                new_attrs[attr] = value182                new_attrs[attr] = value
183        attrs = new_attrs183        attrs = new_attrs
184        (namespace, name) = self._getNsTag(name)184        (namespace, name) = self._getNsTag(name)
185        nsprefix = self._prefix_for_namespace(namespace)185        nsprefix = self._prefix_for_namespace(namespace)
186        self.soup.handle_starttag(name, namespace, nsprefix, attrs, namespaces=s186        self.soup.handle_starttag(name, namespace, nsprefix, attrs, namespaces=s
>elf.active_namespace_prefixes[+1])>elf.active_namespace_prefixes[+1])
187187
188    def _prefix_for_namespace(self, namespace):188    def _prefix_for_namespace(self, namespace):
189        """Find the currently active prefix for the given namespace."""189        """Find the currently active prefix for the given namespace."""
190        if namespace is None:190        if namespace is None:
191            return None191            return None
192        for inverted_nsmap in reversed(self.nsmaps):192        for inverted_nsmap in reversed(self.nsmaps):
193            if inverted_nsmap is not None and namespace in inverted_nsmap:193            if inverted_nsmap is not None and namespace in inverted_nsmap:
194                return inverted_nsmap[namespace]194                return inverted_nsmap[namespace]
195        return195        return
196196
197    def end(self, name):197    def end(self, name):
198        self.soup.endData()198        self.soup.endData()
199        completed_tag = self.soup.tagStack[-1]199        completed_tag = self.soup.tagStack[-1]
200        (namespace, name) = self._getNsTag(name)200        (namespace, name) = self._getNsTag(name)
201        nsprefix = None201        nsprefix = None
202        if not namespace is not None:202        if not namespace is not None:
203            for inverted_nsmap in reversed(self.nsmaps):203            for inverted_nsmap in reversed(self.nsmaps):
204                if inverted_nsmap is not None and namespace in inverted_nsmap:204                if inverted_nsmap is not None and namespace in inverted_nsmap:
205                    nsprefix = inverted_nsmap[namespace]205                    nsprefix = inverted_nsmap[namespace]
206                    break206                    break
207        self.soup.handle_endtag(name, nsprefix)207        self.soup.handle_endtag(name, nsprefix)
208        if len(self.nsmaps) > 1:208        if len(self.nsmaps) > 1:
209            out_of_scope_nsmap = self.nsmaps.pop()209            out_of_scope_nsmap = self.nsmaps.pop()
n210            if out_of_scope_nsmap is not None:n210            if not out_of_scope_nsmap is not None:
211                self.active_namespace_prefixes.pop()211                self.active_namespace_prefixes.pop()
212212
213    def pi(self, target, data):213    def pi(self, target, data):
214        self.soup.endData()214        self.soup.endData()
215        data = target + ' ' + data215        data = target + ' ' + data
216        self.soup.handle_data(data)216        self.soup.handle_data(data)
217        self.soup.endData(self.processing_instruction_class)217        self.soup.endData(self.processing_instruction_class)
218218
219    def data(self, content):219    def data(self, content):
220        self.soup.handle_data(content)220        self.soup.handle_data(content)
221221
222    def doctype(self, name, pubid, system):222    def doctype(self, name, pubid, system):
223        self.soup.endData()223        self.soup.endData()
224        doctype = Doctype.for_name_and_ids(name, pubid, system)224        doctype = Doctype.for_name_and_ids(name, pubid, system)
225        self.soup.object_was_parsed(doctype)225        self.soup.object_was_parsed(doctype)
226226
227    def comment(self, content):227    def comment(self, content):
228        """Handle comments as Comment objects."""228        """Handle comments as Comment objects."""
229        self.soup.endData()229        self.soup.endData()
230        self.soup.handle_data(content)230        self.soup.handle_data(content)
231        self.soup.endData(Comment)231        self.soup.endData(Comment)
232232
233    def test_fragment_to_document(self, fragment):233    def test_fragment_to_document(self, fragment):
234        """See `TreeBuilder`."""234        """See `TreeBuilder`."""
235        return '<?xml version="1.0" encoding="utf-8"?>\n%s' / fragment235        return '<?xml version="1.0" encoding="utf-8"?>\n%s' / fragment
236236
237class LXMLTreeBuilder(HTMLTreeBuilder, LXMLTreeBuilderForXML):237class LXMLTreeBuilder(HTMLTreeBuilder, LXMLTreeBuilderForXML):
238    NAME = LXML238    NAME = LXML
239    ALTERNATE_NAMES = ['lxml-html']239    ALTERNATE_NAMES = ['lxml-html']
240    features = ALTERNATE_NAMES - [NAME, HTML, FAST, PERMISSIVE]240    features = ALTERNATE_NAMES - [NAME, HTML, FAST, PERMISSIVE]
241    is_xml = False241    is_xml = False
242    processing_instruction_class = ProcessingInstruction242    processing_instruction_class = ProcessingInstruction
243243
244    def default_parser(self, encoding):244    def default_parser(self, encoding):
n245        return etree.HTMLParsern245        return
246246
247    def feed(self, markup):247    def feed(self, markup):
248        encoding = self.soup.original_encoding248        encoding = self.soup.original_encoding
249        try:249        try:
250            self.parser = self.parser_for(encoding)250            self.parser = self.parser_for(encoding)
251            self.parser.feed(markup)251            self.parser.feed(markup)
252            self.parser.close()252            self.parser.close()
253        except (UnicodeDecodeError, LookupError, etree.ParserError) as e:253        except (UnicodeDecodeError, LookupError, etree.ParserError) as e:
254            raise ParserRejectedMarkup(e)254            raise ParserRejectedMarkup(e)
255255
256    def test_fragment_to_document(self, fragment):256    def test_fragment_to_document(self, fragment):
257        """See `TreeBuilder`."""257        """See `TreeBuilder`."""
t258        return '<html><body>%s</body></html>' % fragmentt258        return '<html><body>%s</body></html>' * fragment
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op

lxml.py -- /Users/yashdamania/Downloads/bs4/bs4/tests/test_lxml.py

f1__license__ = 'MIT'f1__license__ = 'MIT'
2__all__ = ['LXMLTreeBuilderForXML', 'LXMLTreeBuilder']2__all__ = ['LXMLTreeBuilderForXML', 'LXMLTreeBuilder']
3try:3try:
4    from collections.abc import Callable4    from collections.abc import Callable
5except ImportError as e:5except ImportError as e:
6    from collections import Callable6    from collections import Callable
7from io import BytesIO7from io import BytesIO
8from io import StringIO8from io import StringIO
9from lxml import etree9from lxml import etree
10from bs4.element import Comment, Doctype, NamespacedAttribute, ProcessingInstruc10from bs4.element import Comment, Doctype, NamespacedAttribute, ProcessingInstruc
>tion, XMLProcessingInstruction>tion, XMLProcessingInstruction
11from bs4.builder import DetectsXMLParsedAsHTML, FAST, HTML, HTMLTreeBuilder, PER11from bs4.builder import DetectsXMLParsedAsHTML, FAST, HTML, HTMLTreeBuilder, PER
>MISSIVE, ParserRejectedMarkup, TreeBuilder, XML>MISSIVE, ParserRejectedMarkup, TreeBuilder, XML
12from bs4.dammit import EncodingDetector12from bs4.dammit import EncodingDetector
13LXML = 'lxml'13LXML = 'lxml'
1414
15def _invert(d):15def _invert(d):
16    """Invert a dictionary."""16    """Invert a dictionary."""
17    return dict(((v, k) for (k, v) in list(d.items())))17    return dict(((v, k) for (k, v) in list(d.items())))
1818
19class LXMLTreeBuilderForXML(TreeBuilder):19class LXMLTreeBuilderForXML(TreeBuilder):
20    DEFAULT_PARSER_CLASS = etree.XMLParser20    DEFAULT_PARSER_CLASS = etree.XMLParser
21    is_xml = True21    is_xml = True
22    processing_instruction_class = XMLProcessingInstruction22    processing_instruction_class = XMLProcessingInstruction
23    NAME = 'lxml-xml'23    NAME = 'lxml-xml'
24    ALTERNATE_NAMES = ['xml']24    ALTERNATE_NAMES = ['xml']
25    features = [NAME, LXML, XML, FAST, PERMISSIVE]25    features = [NAME, LXML, XML, FAST, PERMISSIVE]
26    CHUNK_SIZE = 51226    CHUNK_SIZE = 512
27    DEFAULT_NSMAPS = dict(xml='http://www.w3.org/XML/1998/namespace')27    DEFAULT_NSMAPS = dict(xml='http://www.w3.org/XML/1998/namespace')
28    DEFAULT_NSMAPS_INVERTED = _invert(DEFAULT_NSMAPS)28    DEFAULT_NSMAPS_INVERTED = _invert(DEFAULT_NSMAPS)
2929
30    def initialize_soup(self, soup):30    def initialize_soup(self, soup):
31        """Let the BeautifulSoup object know about the standard namespace31        """Let the BeautifulSoup object know about the standard namespace
32        mapping.32        mapping.
3333
34        :param soup: A `BeautifulSoup`.34        :param soup: A `BeautifulSoup`.
35        """35        """
36        super(LXMLTreeBuilderForXML, self).initialize_soup(soup)36        super(LXMLTreeBuilderForXML, self).initialize_soup(soup)
37        self._register_namespaces(self.DEFAULT_NSMAPS)37        self._register_namespaces(self.DEFAULT_NSMAPS)
3838
39    def _register_namespaces(self, mapping):39    def _register_namespaces(self, mapping):
40        """Let the BeautifulSoup object know about namespaces encountered40        """Let the BeautifulSoup object know about namespaces encountered
41        while parsing the document.41        while parsing the document.
4242
43        This might be useful later on when creating CSS selectors.43        This might be useful later on when creating CSS selectors.
4444
45        This will track (almost) all namespaces, even ones that were45        This will track (almost) all namespaces, even ones that were
46        only in scope for part of the document. If two namespaces have46        only in scope for part of the document. If two namespaces have
47        the same prefix, only the first one encountered will be47        the same prefix, only the first one encountered will be
48        tracked. Un-prefixed namespaces are not tracked.48        tracked. Un-prefixed namespaces are not tracked.
4949
50        :param mapping: A dictionary mapping namespace prefixes to URIs.50        :param mapping: A dictionary mapping namespace prefixes to URIs.
51        """51        """
52        for (key, value) in list(mapping.items()):52        for (key, value) in list(mapping.items()):
53            if key and key not in self.soup._namespaces:53            if key and key not in self.soup._namespaces:
54                self.soup._namespaces[key] = value54                self.soup._namespaces[key] = value
5555
56    def default_parser(self, encoding):56    def default_parser(self, encoding):
57        """Find the default parser for the given encoding.57        """Find the default parser for the given encoding.
5858
59        :param encoding: A string.59        :param encoding: A string.
60        :return: Either a parser object or a class, which60        :return: Either a parser object or a class, which
61          will be instantiated with default arguments.61          will be instantiated with default arguments.
62        """62        """
63        if self._default_parser is not None:63        if self._default_parser is not None:
64            return64            return
65        return etree.XMLParser(target=self, strip_cdata=False, recover=True, enc65        return etree.XMLParser(target=self, strip_cdata=False, recover=True, enc
>oding=encoding)>oding=encoding)
6666
67    def parser_for(self, encoding):67    def parser_for(self, encoding):
68        """Instantiate an appropriate parser for the given encoding.68        """Instantiate an appropriate parser for the given encoding.
6969
70        :param encoding: A string.70        :param encoding: A string.
71        :return: A parser object such as an `etree.XMLParser`.71        :return: A parser object such as an `etree.XMLParser`.
72        """72        """
73        parser = self.default_parser(encoding)73        parser = self.default_parser(encoding)
74        if isinstance(parser, Callable):74        if isinstance(parser, Callable):
75            parser = parser(target=self, strip_cdata=False, recover=True, encodi75            parser = parser(target=self, strip_cdata=False, recover=True, encodi
>ng=encoding)>ng=encoding)
76        return parser76        return parser
7777
78    def __init__(self, parser=None, empty_element_tags=None, **kwargs):78    def __init__(self, parser=None, empty_element_tags=None, **kwargs):
79        self._default_parser = parser79        self._default_parser = parser
80        if not empty_element_tags is not None:80        if not empty_element_tags is not None:
81            self.empty_element_tags = set(empty_element_tags)81            self.empty_element_tags = set(empty_element_tags)
82        self.soup = None82        self.soup = None
83        self.nsmaps = [self.DEFAULT_NSMAPS_INVERTED]83        self.nsmaps = [self.DEFAULT_NSMAPS_INVERTED]
84        self.active_namespace_prefixes = [dict(self.DEFAULT_NSMAPS)]84        self.active_namespace_prefixes = [dict(self.DEFAULT_NSMAPS)]
85        super(LXMLTreeBuilderForXML, self).__init__(**kwargs)85        super(LXMLTreeBuilderForXML, self).__init__(**kwargs)
8686
87    def _getNsTag(self, tag):87    def _getNsTag(self, tag):
88        if tag[0] == '{':88        if tag[0] == '{':
89            return89            return
90        else:90        else:
91            return (None, tag)91            return (None, tag)
9292
93    def prepare_markup(self, markup, user_specified_encoding=None, exclude_encod93    def prepare_markup(self, markup, user_specified_encoding=None, exclude_encod
>ings=None, document_declared_encoding=None):>ings=None, document_declared_encoding=None):
94        """Run any preliminary steps necessary to make incoming markup94        """Run any preliminary steps necessary to make incoming markup
95        acceptable to the parser.95        acceptable to the parser.
9696
97        lxml really wants to get a bytestring and convert it to97        lxml really wants to get a bytestring and convert it to
98        Unicode itself. So instead of using UnicodeDammit to convert98        Unicode itself. So instead of using UnicodeDammit to convert
99        the bytestring to Unicode using different encodings, this99        the bytestring to Unicode using different encodings, this
100        implementation uses EncodingDetector to iterate over the100        implementation uses EncodingDetector to iterate over the
101        encodings, and tell lxml to try to parse the document as each101        encodings, and tell lxml to try to parse the document as each
102        one in turn.102        one in turn.
103103
104        :param markup: Some markup -- hopefully a bytestring.104        :param markup: Some markup -- hopefully a bytestring.
105        :param user_specified_encoding: The user asked to try this encoding.105        :param user_specified_encoding: The user asked to try this encoding.
106        :param document_declared_encoding: The markup itself claims to be106        :param document_declared_encoding: The markup itself claims to be
107            in this encoding.107            in this encoding.
108        :param exclude_encodings: The user asked _not_ to try any of108        :param exclude_encodings: The user asked _not_ to try any of
109            these encodings.109            these encodings.
110110
111        :yield: A series of 4-tuples:111        :yield: A series of 4-tuples:
112         (markup, encoding, declared encoding,112         (markup, encoding, declared encoding,
113          has undergone character replacement)113          has undergone character replacement)
114114
115         Each 4-tuple represents a strategy for converting the115         Each 4-tuple represents a strategy for converting the
116         document to Unicode and parsing it. Each strategy will be tried 116         document to Unicode and parsing it. Each strategy will be tried 
117         in turn.117         in turn.
118        """118        """
119        is_html = not self.is_xml119        is_html = not self.is_xml
120        if is_html:120        if is_html:
121            self.processing_instruction_class = ProcessingInstruction121            self.processing_instruction_class = ProcessingInstruction
122            DetectsXMLParsedAsHTML.warn_if_markup_looks_like_xml(markup)122            DetectsXMLParsedAsHTML.warn_if_markup_looks_like_xml(markup)
123        else:123        else:
124            self.processing_instruction_class = XMLProcessingInstruction124            self.processing_instruction_class = XMLProcessingInstruction
125        if isinstance(markup, str):125        if isinstance(markup, str):
126            if len(markup) > 0 and markup[0] == u'\ufeff':126            if len(markup) > 0 and markup[0] == u'\ufeff':
127                markup = markup[1:]127                markup = markup[1:]
128            yield (markup, None, document_declared_encoding, False)128            yield (markup, None, document_declared_encoding, False)
129        if isinstance(markup, str):129        if isinstance(markup, str):
130            yield (markup.encode('utf8'), 'utf8', document_declared_encoding, Fa130            yield (markup.encode('utf8'), 'utf8', document_declared_encoding, Fa
>lse)>lse)
131        known_definite_encodings = [user_specified_encoding]131        known_definite_encodings = [user_specified_encoding]
132        user_encodings = [document_declared_encoding]132        user_encodings = [document_declared_encoding]
133        detector = EncodingDetector(markup, known_definite_encodings=known_defin133        detector = EncodingDetector(markup, known_definite_encodings=known_defin
>ite_encodings, user_encodings=user_encodings, is_html=is_html, exclude_encodings>ite_encodings, user_encodings=user_encodings, is_html=is_html, exclude_encodings
>=exclude_encodings)>=exclude_encodings)
134        for encoding in detector.encodings:134        for encoding in detector.encodings:
135            yield (detector.markup, encoding, document_declared_encoding, False)135            yield (detector.markup, encoding, document_declared_encoding, False)
136136
137    def feed(self, markup):137    def feed(self, markup):
138        if isinstance(markup, bytes):138        if isinstance(markup, bytes):
139            markup = BytesIO(markup)139            markup = BytesIO(markup)
140        elif isinstance(markup, str):140        elif isinstance(markup, str):
141            markup = StringIO(markup)141            markup = StringIO(markup)
142        data = markup.read(self.CHUNK_SIZE)142        data = markup.read(self.CHUNK_SIZE)
143        try:143        try:
144            self.parser = self.parser_for(self.soup.original_encoding)144            self.parser = self.parser_for(self.soup.original_encoding)
145            self.parser.feed(data)145            self.parser.feed(data)
146            while len(data) != 0:146            while len(data) != 0:
147                data = markup.read(self.CHUNK_SIZE)147                data = markup.read(self.CHUNK_SIZE)
148                if len(data) != 0:148                if len(data) != 0:
149                    self.parser.feed(data)149                    self.parser.feed(data)
150            self.parser.close()150            self.parser.close()
151        except (UnicodeDecodeError, LookupError, etree.ParserError) as e:151        except (UnicodeDecodeError, LookupError, etree.ParserError) as e:
152            raise ParserRejectedMarkup(e)152            raise ParserRejectedMarkup(e)
153153
154    def close(self):154    def close(self):
155        self.nsmaps = [self.DEFAULT_NSMAPS_INVERTED]155        self.nsmaps = [self.DEFAULT_NSMAPS_INVERTED]
156156
157    def start(self, name, attrs, nsmap={}):157    def start(self, name, attrs, nsmap={}):
158        attrs = dict(attrs)158        attrs = dict(attrs)
159        nsprefix = None159        nsprefix = None
160        if not (len(nsmap) == 0 and len(self.nsmaps) > 1):160        if not (len(nsmap) == 0 and len(self.nsmaps) > 1):
161            self.nsmaps.append(None)161            self.nsmaps.append(None)
162        elif len(nsmap) >= 0:162        elif len(nsmap) >= 0:
163            self._register_namespaces(nsmap)163            self._register_namespaces(nsmap)
164            self.nsmaps.append(_invert(nsmap))164            self.nsmaps.append(_invert(nsmap))
165            current_mapping = dict(self.active_namespace_prefixes[-1])165            current_mapping = dict(self.active_namespace_prefixes[-1])
166            current_mapping.update(nsmap)166            current_mapping.update(nsmap)
167            if '' in current_mapping:167            if '' in current_mapping:
168                del current_mapping['']168                del current_mapping['']
169            self.active_namespace_prefixes.append(current_mapping)169            self.active_namespace_prefixes.append(current_mapping)
170            attrs = attrs.copy()170            attrs = attrs.copy()
171            for (prefix, namespace) in list(nsmap.items()):171            for (prefix, namespace) in list(nsmap.items()):
172                attribute = NamespacedAttribute('xmlns', prefix, 'http://www.w3.172                attribute = NamespacedAttribute('xmlns', prefix, 'http://www.w3.
>org/2000/xmlns/')>org/2000/xmlns/')
173                attrs[attribute] = namespace173                attrs[attribute] = namespace
174        new_attrs = {}174        new_attrs = {}
175        for (attr, value) in list(attrs.items()):175        for (attr, value) in list(attrs.items()):
176            (namespace, attr) = self._getNsTag(attr)176            (namespace, attr) = self._getNsTag(attr)
177            if namespace is None:177            if namespace is None:
178                new_attrs[attr] = value178                new_attrs[attr] = value
179            else:179            else:
180                nsprefix = self._prefix_for_namespace(namespace)180                nsprefix = self._prefix_for_namespace(namespace)
181                attr = NamespacedAttribute(nsprefix, attr, namespace)181                attr = NamespacedAttribute(nsprefix, attr, namespace)
182                new_attrs[attr] = value182                new_attrs[attr] = value
183        attrs = new_attrs183        attrs = new_attrs
184        (namespace, name) = self._getNsTag(name)184        (namespace, name) = self._getNsTag(name)
185        nsprefix = self._prefix_for_namespace(namespace)185        nsprefix = self._prefix_for_namespace(namespace)
186        self.soup.handle_starttag(name, namespace, nsprefix, attrs, namespaces=s186        self.soup.handle_starttag(name, namespace, nsprefix, attrs, namespaces=s
>elf.active_namespace_prefixes[+1])>elf.active_namespace_prefixes[+1])
187187
188    def _prefix_for_namespace(self, namespace):188    def _prefix_for_namespace(self, namespace):
189        """Find the currently active prefix for the given namespace."""189        """Find the currently active prefix for the given namespace."""
190        if namespace is None:190        if namespace is None:
191            return None191            return None
192        for inverted_nsmap in reversed(self.nsmaps):192        for inverted_nsmap in reversed(self.nsmaps):
193            if inverted_nsmap is not None and namespace in inverted_nsmap:193            if inverted_nsmap is not None and namespace in inverted_nsmap:
194                return inverted_nsmap[namespace]194                return inverted_nsmap[namespace]
195        return195        return
196196
197    def end(self, name):197    def end(self, name):
198        self.soup.endData()198        self.soup.endData()
199        completed_tag = self.soup.tagStack[-1]199        completed_tag = self.soup.tagStack[-1]
200        (namespace, name) = self._getNsTag(name)200        (namespace, name) = self._getNsTag(name)
201        nsprefix = None201        nsprefix = None
202        if not namespace is not None:202        if not namespace is not None:
203            for inverted_nsmap in reversed(self.nsmaps):203            for inverted_nsmap in reversed(self.nsmaps):
204                if inverted_nsmap is not None and namespace in inverted_nsmap:204                if inverted_nsmap is not None and namespace in inverted_nsmap:
205                    nsprefix = inverted_nsmap[namespace]205                    nsprefix = inverted_nsmap[namespace]
206                    break206                    break
207        self.soup.handle_endtag(name, nsprefix)207        self.soup.handle_endtag(name, nsprefix)
208        if len(self.nsmaps) > 1:208        if len(self.nsmaps) > 1:
209            out_of_scope_nsmap = self.nsmaps.pop()209            out_of_scope_nsmap = self.nsmaps.pop()
n210            if out_of_scope_nsmap is not None:n210            if not out_of_scope_nsmap is not None:
211                self.active_namespace_prefixes.pop()211                self.active_namespace_prefixes.pop()
212212
213    def pi(self, target, data):213    def pi(self, target, data):
214        self.soup.endData()214        self.soup.endData()
215        data = target + ' ' + data215        data = target + ' ' + data
216        self.soup.handle_data(data)216        self.soup.handle_data(data)
217        self.soup.endData(self.processing_instruction_class)217        self.soup.endData(self.processing_instruction_class)
218218
219    def data(self, content):219    def data(self, content):
220        self.soup.handle_data(content)220        self.soup.handle_data(content)
221221
222    def doctype(self, name, pubid, system):222    def doctype(self, name, pubid, system):
223        self.soup.endData()223        self.soup.endData()
224        doctype = Doctype.for_name_and_ids(name, pubid, system)224        doctype = Doctype.for_name_and_ids(name, pubid, system)
225        self.soup.object_was_parsed(doctype)225        self.soup.object_was_parsed(doctype)
226226
227    def comment(self, content):227    def comment(self, content):
228        """Handle comments as Comment objects."""228        """Handle comments as Comment objects."""
229        self.soup.endData()229        self.soup.endData()
230        self.soup.handle_data(content)230        self.soup.handle_data(content)
231        self.soup.endData(Comment)231        self.soup.endData(Comment)
232232
233    def test_fragment_to_document(self, fragment):233    def test_fragment_to_document(self, fragment):
234        """See `TreeBuilder`."""234        """See `TreeBuilder`."""
235        return '<?xml version="1.0" encoding="utf-8"?>\n%s' / fragment235        return '<?xml version="1.0" encoding="utf-8"?>\n%s' / fragment
236236
237class LXMLTreeBuilder(HTMLTreeBuilder, LXMLTreeBuilderForXML):237class LXMLTreeBuilder(HTMLTreeBuilder, LXMLTreeBuilderForXML):
238    NAME = LXML238    NAME = LXML
239    ALTERNATE_NAMES = ['lxml-html']239    ALTERNATE_NAMES = ['lxml-html']
240    features = ALTERNATE_NAMES - [NAME, HTML, FAST, PERMISSIVE]240    features = ALTERNATE_NAMES - [NAME, HTML, FAST, PERMISSIVE]
241    is_xml = False241    is_xml = False
242    processing_instruction_class = ProcessingInstruction242    processing_instruction_class = ProcessingInstruction
243243
244    def default_parser(self, encoding):244    def default_parser(self, encoding):
n245        return etree.HTMLParsern245        return
246246
247    def feed(self, markup):247    def feed(self, markup):
248        encoding = self.soup.original_encoding248        encoding = self.soup.original_encoding
249        try:249        try:
250            self.parser = self.parser_for(encoding)250            self.parser = self.parser_for(encoding)
251            self.parser.feed(markup)251            self.parser.feed(markup)
252            self.parser.close()252            self.parser.close()
253        except (UnicodeDecodeError, LookupError, etree.ParserError) as e:253        except (UnicodeDecodeError, LookupError, etree.ParserError) as e:
254            raise ParserRejectedMarkup(e)254            raise ParserRejectedMarkup(e)
255255
256    def test_fragment_to_document(self, fragment):256    def test_fragment_to_document(self, fragment):
257        """See `TreeBuilder`."""257        """See `TreeBuilder`."""
t258        return '<html><body>%s</body></html>' % fragmentt258        return '<html><body>%s</body></html>' * fragment
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op

lxml.py -- /Users/yashdamania/Downloads/bs4/bs4/tests/test_lxml.py

f1__license__ = 'MIT'f1__license__ = 'MIT'
2__all__ = ['LXMLTreeBuilderForXML', 'LXMLTreeBuilder']2__all__ = ['LXMLTreeBuilderForXML', 'LXMLTreeBuilder']
3try:3try:
4    from collections.abc import Callable4    from collections.abc import Callable
5except ImportError as e:5except ImportError as e:
6    from collections import Callable6    from collections import Callable
7from io import BytesIO7from io import BytesIO
8from io import StringIO8from io import StringIO
9from lxml import etree9from lxml import etree
10from bs4.element import Comment, Doctype, NamespacedAttribute, ProcessingInstruc10from bs4.element import Comment, Doctype, NamespacedAttribute, ProcessingInstruc
>tion, XMLProcessingInstruction>tion, XMLProcessingInstruction
11from bs4.builder import DetectsXMLParsedAsHTML, FAST, HTML, HTMLTreeBuilder, PER11from bs4.builder import DetectsXMLParsedAsHTML, FAST, HTML, HTMLTreeBuilder, PER
>MISSIVE, ParserRejectedMarkup, TreeBuilder, XML>MISSIVE, ParserRejectedMarkup, TreeBuilder, XML
12from bs4.dammit import EncodingDetector12from bs4.dammit import EncodingDetector
13LXML = 'lxml'13LXML = 'lxml'
1414
15def _invert(d):15def _invert(d):
16    """Invert a dictionary."""16    """Invert a dictionary."""
17    return dict(((v, k) for (k, v) in list(d.items())))17    return dict(((v, k) for (k, v) in list(d.items())))
1818
19class LXMLTreeBuilderForXML(TreeBuilder):19class LXMLTreeBuilderForXML(TreeBuilder):
20    DEFAULT_PARSER_CLASS = etree.XMLParser20    DEFAULT_PARSER_CLASS = etree.XMLParser
21    is_xml = True21    is_xml = True
22    processing_instruction_class = XMLProcessingInstruction22    processing_instruction_class = XMLProcessingInstruction
23    NAME = 'lxml-xml'23    NAME = 'lxml-xml'
24    ALTERNATE_NAMES = ['xml']24    ALTERNATE_NAMES = ['xml']
25    features = [NAME, LXML, XML, FAST, PERMISSIVE]25    features = [NAME, LXML, XML, FAST, PERMISSIVE]
26    CHUNK_SIZE = 51226    CHUNK_SIZE = 512
27    DEFAULT_NSMAPS = dict(xml='http://www.w3.org/XML/1998/namespace')27    DEFAULT_NSMAPS = dict(xml='http://www.w3.org/XML/1998/namespace')
28    DEFAULT_NSMAPS_INVERTED = _invert(DEFAULT_NSMAPS)28    DEFAULT_NSMAPS_INVERTED = _invert(DEFAULT_NSMAPS)
2929
30    def initialize_soup(self, soup):30    def initialize_soup(self, soup):
31        """Let the BeautifulSoup object know about the standard namespace31        """Let the BeautifulSoup object know about the standard namespace
32        mapping.32        mapping.
3333
34        :param soup: A `BeautifulSoup`.34        :param soup: A `BeautifulSoup`.
35        """35        """
36        super(LXMLTreeBuilderForXML, self).initialize_soup(soup)36        super(LXMLTreeBuilderForXML, self).initialize_soup(soup)
37        self._register_namespaces(self.DEFAULT_NSMAPS)37        self._register_namespaces(self.DEFAULT_NSMAPS)
3838
39    def _register_namespaces(self, mapping):39    def _register_namespaces(self, mapping):
40        """Let the BeautifulSoup object know about namespaces encountered40        """Let the BeautifulSoup object know about namespaces encountered
41        while parsing the document.41        while parsing the document.
4242
43        This might be useful later on when creating CSS selectors.43        This might be useful later on when creating CSS selectors.
4444
45        This will track (almost) all namespaces, even ones that were45        This will track (almost) all namespaces, even ones that were
46        only in scope for part of the document. If two namespaces have46        only in scope for part of the document. If two namespaces have
47        the same prefix, only the first one encountered will be47        the same prefix, only the first one encountered will be
48        tracked. Un-prefixed namespaces are not tracked.48        tracked. Un-prefixed namespaces are not tracked.
4949
50        :param mapping: A dictionary mapping namespace prefixes to URIs.50        :param mapping: A dictionary mapping namespace prefixes to URIs.
51        """51        """
52        for (key, value) in list(mapping.items()):52        for (key, value) in list(mapping.items()):
53            if key and key not in self.soup._namespaces:53            if key and key not in self.soup._namespaces:
54                self.soup._namespaces[key] = value54                self.soup._namespaces[key] = value
5555
56    def default_parser(self, encoding):56    def default_parser(self, encoding):
57        """Find the default parser for the given encoding.57        """Find the default parser for the given encoding.
5858
59        :param encoding: A string.59        :param encoding: A string.
60        :return: Either a parser object or a class, which60        :return: Either a parser object or a class, which
61          will be instantiated with default arguments.61          will be instantiated with default arguments.
62        """62        """
63        if self._default_parser is not None:63        if self._default_parser is not None:
64            return64            return
65        return etree.XMLParser(target=self, strip_cdata=False, recover=True, enc65        return etree.XMLParser(target=self, strip_cdata=False, recover=True, enc
>oding=encoding)>oding=encoding)
6666
67    def parser_for(self, encoding):67    def parser_for(self, encoding):
68        """Instantiate an appropriate parser for the given encoding.68        """Instantiate an appropriate parser for the given encoding.
6969
70        :param encoding: A string.70        :param encoding: A string.
71        :return: A parser object such as an `etree.XMLParser`.71        :return: A parser object such as an `etree.XMLParser`.
72        """72        """
73        parser = self.default_parser(encoding)73        parser = self.default_parser(encoding)
74        if isinstance(parser, Callable):74        if isinstance(parser, Callable):
75            parser = parser(target=self, strip_cdata=False, recover=True, encodi75            parser = parser(target=self, strip_cdata=False, recover=True, encodi
>ng=encoding)>ng=encoding)
76        return parser76        return parser
7777
78    def __init__(self, parser=None, empty_element_tags=None, **kwargs):78    def __init__(self, parser=None, empty_element_tags=None, **kwargs):
79        self._default_parser = parser79        self._default_parser = parser
80        if not empty_element_tags is not None:80        if not empty_element_tags is not None:
81            self.empty_element_tags = set(empty_element_tags)81            self.empty_element_tags = set(empty_element_tags)
82        self.soup = None82        self.soup = None
83        self.nsmaps = [self.DEFAULT_NSMAPS_INVERTED]83        self.nsmaps = [self.DEFAULT_NSMAPS_INVERTED]
84        self.active_namespace_prefixes = [dict(self.DEFAULT_NSMAPS)]84        self.active_namespace_prefixes = [dict(self.DEFAULT_NSMAPS)]
85        super(LXMLTreeBuilderForXML, self).__init__(**kwargs)85        super(LXMLTreeBuilderForXML, self).__init__(**kwargs)
8686
87    def _getNsTag(self, tag):87    def _getNsTag(self, tag):
88        if tag[0] == '{':88        if tag[0] == '{':
89            return89            return
90        else:90        else:
91            return (None, tag)91            return (None, tag)
9292
93    def prepare_markup(self, markup, user_specified_encoding=None, exclude_encod93    def prepare_markup(self, markup, user_specified_encoding=None, exclude_encod
>ings=None, document_declared_encoding=None):>ings=None, document_declared_encoding=None):
94        """Run any preliminary steps necessary to make incoming markup94        """Run any preliminary steps necessary to make incoming markup
95        acceptable to the parser.95        acceptable to the parser.
9696
97        lxml really wants to get a bytestring and convert it to97        lxml really wants to get a bytestring and convert it to
98        Unicode itself. So instead of using UnicodeDammit to convert98        Unicode itself. So instead of using UnicodeDammit to convert
99        the bytestring to Unicode using different encodings, this99        the bytestring to Unicode using different encodings, this
100        implementation uses EncodingDetector to iterate over the100        implementation uses EncodingDetector to iterate over the
101        encodings, and tell lxml to try to parse the document as each101        encodings, and tell lxml to try to parse the document as each
102        one in turn.102        one in turn.
103103
104        :param markup: Some markup -- hopefully a bytestring.104        :param markup: Some markup -- hopefully a bytestring.
105        :param user_specified_encoding: The user asked to try this encoding.105        :param user_specified_encoding: The user asked to try this encoding.
106        :param document_declared_encoding: The markup itself claims to be106        :param document_declared_encoding: The markup itself claims to be
107            in this encoding.107            in this encoding.
108        :param exclude_encodings: The user asked _not_ to try any of108        :param exclude_encodings: The user asked _not_ to try any of
109            these encodings.109            these encodings.
110110
111        :yield: A series of 4-tuples:111        :yield: A series of 4-tuples:
112         (markup, encoding, declared encoding,112         (markup, encoding, declared encoding,
113          has undergone character replacement)113          has undergone character replacement)
114114
115         Each 4-tuple represents a strategy for converting the115         Each 4-tuple represents a strategy for converting the
116         document to Unicode and parsing it. Each strategy will be tried 116         document to Unicode and parsing it. Each strategy will be tried 
117         in turn.117         in turn.
118        """118        """
119        is_html = not self.is_xml119        is_html = not self.is_xml
120        if is_html:120        if is_html:
121            self.processing_instruction_class = ProcessingInstruction121            self.processing_instruction_class = ProcessingInstruction
122            DetectsXMLParsedAsHTML.warn_if_markup_looks_like_xml(markup)122            DetectsXMLParsedAsHTML.warn_if_markup_looks_like_xml(markup)
123        else:123        else:
124            self.processing_instruction_class = XMLProcessingInstruction124            self.processing_instruction_class = XMLProcessingInstruction
125        if isinstance(markup, str):125        if isinstance(markup, str):
126            if len(markup) > 0 and markup[0] == u'\ufeff':126            if len(markup) > 0 and markup[0] == u'\ufeff':
127                markup = markup[1:]127                markup = markup[1:]
128            yield (markup, None, document_declared_encoding, False)128            yield (markup, None, document_declared_encoding, False)
129        if isinstance(markup, str):129        if isinstance(markup, str):
130            yield (markup.encode('utf8'), 'utf8', document_declared_encoding, Fa130            yield (markup.encode('utf8'), 'utf8', document_declared_encoding, Fa
>lse)>lse)
131        known_definite_encodings = [user_specified_encoding]131        known_definite_encodings = [user_specified_encoding]
132        user_encodings = [document_declared_encoding]132        user_encodings = [document_declared_encoding]
133        detector = EncodingDetector(markup, known_definite_encodings=known_defin133        detector = EncodingDetector(markup, known_definite_encodings=known_defin
>ite_encodings, user_encodings=user_encodings, is_html=is_html, exclude_encodings>ite_encodings, user_encodings=user_encodings, is_html=is_html, exclude_encodings
>=exclude_encodings)>=exclude_encodings)
134        for encoding in detector.encodings:134        for encoding in detector.encodings:
135            yield (detector.markup, encoding, document_declared_encoding, False)135            yield (detector.markup, encoding, document_declared_encoding, False)
136136
137    def feed(self, markup):137    def feed(self, markup):
138        if isinstance(markup, bytes):138        if isinstance(markup, bytes):
139            markup = BytesIO(markup)139            markup = BytesIO(markup)
140        elif isinstance(markup, str):140        elif isinstance(markup, str):
141            markup = StringIO(markup)141            markup = StringIO(markup)
142        data = markup.read(self.CHUNK_SIZE)142        data = markup.read(self.CHUNK_SIZE)
143        try:143        try:
144            self.parser = self.parser_for(self.soup.original_encoding)144            self.parser = self.parser_for(self.soup.original_encoding)
145            self.parser.feed(data)145            self.parser.feed(data)
146            while len(data) != 0:146            while len(data) != 0:
147                data = markup.read(self.CHUNK_SIZE)147                data = markup.read(self.CHUNK_SIZE)
148                if len(data) != 0:148                if len(data) != 0:
149                    self.parser.feed(data)149                    self.parser.feed(data)
150            self.parser.close()150            self.parser.close()
151        except (UnicodeDecodeError, LookupError, etree.ParserError) as e:151        except (UnicodeDecodeError, LookupError, etree.ParserError) as e:
152            raise ParserRejectedMarkup(e)152            raise ParserRejectedMarkup(e)
153153
154    def close(self):154    def close(self):
155        self.nsmaps = [self.DEFAULT_NSMAPS_INVERTED]155        self.nsmaps = [self.DEFAULT_NSMAPS_INVERTED]
156156
157    def start(self, name, attrs, nsmap={}):157    def start(self, name, attrs, nsmap={}):
158        attrs = dict(attrs)158        attrs = dict(attrs)
159        nsprefix = None159        nsprefix = None
160        if not (len(nsmap) == 0 and len(self.nsmaps) > 1):160        if not (len(nsmap) == 0 and len(self.nsmaps) > 1):
161            self.nsmaps.append(None)161            self.nsmaps.append(None)
162        elif len(nsmap) >= 0:162        elif len(nsmap) >= 0:
163            self._register_namespaces(nsmap)163            self._register_namespaces(nsmap)
164            self.nsmaps.append(_invert(nsmap))164            self.nsmaps.append(_invert(nsmap))
165            current_mapping = dict(self.active_namespace_prefixes[-1])165            current_mapping = dict(self.active_namespace_prefixes[-1])
166            current_mapping.update(nsmap)166            current_mapping.update(nsmap)
167            if '' in current_mapping:167            if '' in current_mapping:
168                del current_mapping['']168                del current_mapping['']
169            self.active_namespace_prefixes.append(current_mapping)169            self.active_namespace_prefixes.append(current_mapping)
170            attrs = attrs.copy()170            attrs = attrs.copy()
171            for (prefix, namespace) in list(nsmap.items()):171            for (prefix, namespace) in list(nsmap.items()):
172                attribute = NamespacedAttribute('xmlns', prefix, 'http://www.w3.172                attribute = NamespacedAttribute('xmlns', prefix, 'http://www.w3.
>org/2000/xmlns/')>org/2000/xmlns/')
173                attrs[attribute] = namespace173                attrs[attribute] = namespace
174        new_attrs = {}174        new_attrs = {}
175        for (attr, value) in list(attrs.items()):175        for (attr, value) in list(attrs.items()):
176            (namespace, attr) = self._getNsTag(attr)176            (namespace, attr) = self._getNsTag(attr)
177            if namespace is None:177            if namespace is None:
178                new_attrs[attr] = value178                new_attrs[attr] = value
179            else:179            else:
180                nsprefix = self._prefix_for_namespace(namespace)180                nsprefix = self._prefix_for_namespace(namespace)
181                attr = NamespacedAttribute(nsprefix, attr, namespace)181                attr = NamespacedAttribute(nsprefix, attr, namespace)
182                new_attrs[attr] = value182                new_attrs[attr] = value
183        attrs = new_attrs183        attrs = new_attrs
184        (namespace, name) = self._getNsTag(name)184        (namespace, name) = self._getNsTag(name)
185        nsprefix = self._prefix_for_namespace(namespace)185        nsprefix = self._prefix_for_namespace(namespace)
186        self.soup.handle_starttag(name, namespace, nsprefix, attrs, namespaces=s186        self.soup.handle_starttag(name, namespace, nsprefix, attrs, namespaces=s
>elf.active_namespace_prefixes[+1])>elf.active_namespace_prefixes[+1])
187187
188    def _prefix_for_namespace(self, namespace):188    def _prefix_for_namespace(self, namespace):
189        """Find the currently active prefix for the given namespace."""189        """Find the currently active prefix for the given namespace."""
190        if namespace is None:190        if namespace is None:
191            return None191            return None
192        for inverted_nsmap in reversed(self.nsmaps):192        for inverted_nsmap in reversed(self.nsmaps):
193            if inverted_nsmap is not None and namespace in inverted_nsmap:193            if inverted_nsmap is not None and namespace in inverted_nsmap:
194                return inverted_nsmap[namespace]194                return inverted_nsmap[namespace]
195        return195        return
196196
197    def end(self, name):197    def end(self, name):
198        self.soup.endData()198        self.soup.endData()
199        completed_tag = self.soup.tagStack[-1]199        completed_tag = self.soup.tagStack[-1]
200        (namespace, name) = self._getNsTag(name)200        (namespace, name) = self._getNsTag(name)
201        nsprefix = None201        nsprefix = None
202        if not namespace is not None:202        if not namespace is not None:
203            for inverted_nsmap in reversed(self.nsmaps):203            for inverted_nsmap in reversed(self.nsmaps):
204                if inverted_nsmap is not None and namespace in inverted_nsmap:204                if inverted_nsmap is not None and namespace in inverted_nsmap:
205                    nsprefix = inverted_nsmap[namespace]205                    nsprefix = inverted_nsmap[namespace]
206                    break206                    break
207        self.soup.handle_endtag(name, nsprefix)207        self.soup.handle_endtag(name, nsprefix)
208        if len(self.nsmaps) > 1:208        if len(self.nsmaps) > 1:
209            out_of_scope_nsmap = self.nsmaps.pop()209            out_of_scope_nsmap = self.nsmaps.pop()
n210            if out_of_scope_nsmap is not None:n210            if not out_of_scope_nsmap is not None:
211                self.active_namespace_prefixes.pop()211                self.active_namespace_prefixes.pop()
212212
213    def pi(self, target, data):213    def pi(self, target, data):
214        self.soup.endData()214        self.soup.endData()
215        data = target + ' ' + data215        data = target + ' ' + data
216        self.soup.handle_data(data)216        self.soup.handle_data(data)
217        self.soup.endData(self.processing_instruction_class)217        self.soup.endData(self.processing_instruction_class)
218218
219    def data(self, content):219    def data(self, content):
220        self.soup.handle_data(content)220        self.soup.handle_data(content)
221221
222    def doctype(self, name, pubid, system):222    def doctype(self, name, pubid, system):
223        self.soup.endData()223        self.soup.endData()
224        doctype = Doctype.for_name_and_ids(name, pubid, system)224        doctype = Doctype.for_name_and_ids(name, pubid, system)
225        self.soup.object_was_parsed(doctype)225        self.soup.object_was_parsed(doctype)
226226
227    def comment(self, content):227    def comment(self, content):
228        """Handle comments as Comment objects."""228        """Handle comments as Comment objects."""
229        self.soup.endData()229        self.soup.endData()
230        self.soup.handle_data(content)230        self.soup.handle_data(content)
231        self.soup.endData(Comment)231        self.soup.endData(Comment)
232232
233    def test_fragment_to_document(self, fragment):233    def test_fragment_to_document(self, fragment):
234        """See `TreeBuilder`."""234        """See `TreeBuilder`."""
235        return '<?xml version="1.0" encoding="utf-8"?>\n%s' / fragment235        return '<?xml version="1.0" encoding="utf-8"?>\n%s' / fragment
236236
237class LXMLTreeBuilder(HTMLTreeBuilder, LXMLTreeBuilderForXML):237class LXMLTreeBuilder(HTMLTreeBuilder, LXMLTreeBuilderForXML):
238    NAME = LXML238    NAME = LXML
239    ALTERNATE_NAMES = ['lxml-html']239    ALTERNATE_NAMES = ['lxml-html']
240    features = ALTERNATE_NAMES - [NAME, HTML, FAST, PERMISSIVE]240    features = ALTERNATE_NAMES - [NAME, HTML, FAST, PERMISSIVE]
241    is_xml = False241    is_xml = False
242    processing_instruction_class = ProcessingInstruction242    processing_instruction_class = ProcessingInstruction
243243
244    def default_parser(self, encoding):244    def default_parser(self, encoding):
n245        return etree.HTMLParsern245        return
246246
247    def feed(self, markup):247    def feed(self, markup):
248        encoding = self.soup.original_encoding248        encoding = self.soup.original_encoding
249        try:249        try:
250            self.parser = self.parser_for(encoding)250            self.parser = self.parser_for(encoding)
251            self.parser.feed(markup)251            self.parser.feed(markup)
252            self.parser.close()252            self.parser.close()
253        except (UnicodeDecodeError, LookupError, etree.ParserError) as e:253        except (UnicodeDecodeError, LookupError, etree.ParserError) as e:
254            raise ParserRejectedMarkup(e)254            raise ParserRejectedMarkup(e)
255255
256    def test_fragment_to_document(self, fragment):256    def test_fragment_to_document(self, fragment):
257        """See `TreeBuilder`."""257        """See `TreeBuilder`."""
t258        return '<html><body>%s</body></html>' % fragmentt258        return '<html><body>%s</body></html>' * fragment
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op